diff --git a/action/ajax.php b/action/ajax.php
new file mode 100644
--- /dev/null
+++ b/action/ajax.php
@@ -0,0 +1,103 @@
+hlp =& plugin_load('helper','davcard');
+ }
+
+ function register(Doku_Event_Handler $controller) {
+ $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown');
+ }
+
+ function handle_ajax_call_unknown(Doku_Event $event, $param) {
+ if($event->data != 'plugin_davcard') return;
+
+ $event->preventDefault();
+ $event->stopPropagation();
+ global $INPUT;
+
+ $action = trim($INPUT->post->str('action'));
+ $id = trim($INPUT->post->str('id'));
+ $name = trim($INPUT->post->str('name'));
+
+ if(!checkSecurityToken())
+ {
+ echo "CSRF Attack.";
+ return;
+ }
+
+ $data = array();
+
+ $data['result'] = false;
+ $data['html'] = $this->getLang('unknown_error');
+
+ // Parse the requested action
+ switch($action)
+ {
+ case 'getContact':
+ if($name !== '')
+ {
+ $info = $this->hlp->getContactByName($id, $name);
+ if($info === false)
+ {
+ $data['html'] = $this->getLang('contact_not_found');
+ break;
+ }
+ $data['result'] = true;
+ if($info['formattedname'] !== '')
+ $data['html'] = 'Name: '.$info['formattedname'].'
';
+ if(count($info['tel']) > 0)
+ {
+ foreach($info['tel'] as $type => $nr)
+ {
+ $data['html'] .= $type.': '.$nr.'
';
+ }
+ }
+ if(count($info['addr']) > 0)
+ {
+ foreach($info['addr'] as $type => $addr)
+ {
+ $data['html'] .= $type.': '.join('
', $addr);
+ $data['html'] .= '
';
+ }
+ }
+ if(count($info['mail']) > 0)
+ {
+ foreach($info['mail'] as $type => $mail)
+ {
+ $data['html'] .= $type.': '.$mail.'
';
+ }
+ }
+
+ }
+ else
+ {
+ $data['html'] = $this->hlp-->getContactByUri($id);
+ }
+ break;
+ }
+
+ // If we are still here, JSON output is requested
+
+ //json library of DokuWiki
+ require_once DOKU_INC . 'inc/JSON.php';
+ $json = new JSON();
+
+ //set content type
+ header('Content-Type: application/json');
+ echo $json->encode($data);
+ }
+
+}
diff --git a/action/jsinfo.php b/action/jsinfo.php
new file mode 100644
--- /dev/null
+++ b/action/jsinfo.php
@@ -0,0 +1,23 @@
+register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'add_jsinfo_information');
+ }
+
+ /**
+ * Add the language variable to the JSINFO variable
+ */
+ function add_jsinfo_information(Doku_Event $event, $param) {
+ global $JSINFO;
+
+ $JSINFO['plugin']['davcard']['sectok'] = getSecurityToken();
+ }
+}
diff --git a/conf/default.php b/conf/default.php
new file mode 100644
--- /dev/null
+++ b/conf/default.php
@@ -0,0 +1,8 @@
+
+ */
+
+
diff --git a/conf/metadata.php b/conf/metadata.php
new file mode 100644
--- /dev/null
+++ b/conf/metadata.php
@@ -0,0 +1,7 @@
+
+ */
+
diff --git a/helper.php b/helper.php
new file mode 100644
--- /dev/null
+++ b/helper.php
@@ -0,0 +1,114 @@
+getLang('no_wdc');
+ $connectionId = str_replace('webdav://', '', $id);
+ $settings = $wdc->getConnection($connectionId);
+
+ if($settings === false)
+ return $this->getLang('settings_not_found');
+ if($settings['type'] !== 'contacts')
+ return $this->getLang('wrong_type');
+
+ $entries = $wdc->getAddressbookEntries($connectionId);
+ foreach($entries as $entry)
+ {
+ if(trim($entry['formattedname']) == $name)
+ {
+ $info = $this->parseVcard($entry['contactdata']);
+ return $info;
+ }
+ }
+ }
+ return false;
+ }
+
+ public function getContactByUri($id)
+ {
+
+ }
+
+ private function parseVcard($card)
+ {
+ require_once(DOKU_PLUGIN.'davcard/vendor/autoload.php');
+
+ $vObject = \Sabre\VObject\Reader::read($card);
+
+ $formattedname = '';
+ $structuredname = '';
+ $tel = array();
+ $addr = array();
+ $mail = array();
+
+ if(isset($vObject->FN))
+ $formattedname = (string)$vObject->FN;
+
+ if(isset($vObject->N))
+ $structuredname = join(';', $vObject->N->getParts());
+
+ if(isset($vObject->TEL))
+ {
+ foreach($vObject->TEL as $number)
+ {
+ if(isset($number['TYPE']))
+ $tel[(string)$number['TYPE']] = (string)$number;
+ else
+ $tel[] = (string)$number;
+ }
+ }
+
+ if(isset($vObject->ADR))
+ {
+ foreach($vObject->ADR as $adr)
+ {
+ if(isset($adr['TYPE']))
+ $addr[(string)$adr['TYPE']] = $adr->getParts();
+ else
+ $addr[] = $adr->getParts();
+ }
+ }
+
+ if(isset($vObject->EMAIL))
+ {
+ foreach($vObject->EMAIL as $email)
+ {
+ if(isset($email['TYPE']))
+ $mail[(string)$email['TYPE']] = (string)$email;
+ else
+ $mail[] = (string)$email;
+ }
+ }
+
+
+ return array(
+ 'formattedname' => $formattedname,
+ 'structuredname' => $structuredname,
+ 'tel' => $tel,
+ 'mail' => $mail,
+ 'addr' => $addr
+ );
+ }
+
+}
diff --git a/lang/en/lang.php b/lang/en/lang.php
new file mode 100644
--- /dev/null
+++ b/lang/en/lang.php
@@ -0,0 +1,13 @@
+
+ */
+$lang['unknown_error'] = 'Unknown Error';
+$lang['id_name_not_set'] = 'Either ID or Name must be set';
+$lang['loading_via_ajax'] = 'Loadig Contact Data...';
+$lang['wdc'] = 'Loading webdavclient PlugIn failed.';
+$lang['settings_not_found'] = 'The requested WebDAV connection was not found';
+$lang['wrong_type'] = 'The requested WebDAV connection is not of type contact';
+$lang['contact_not_found'] = 'The requested contact was not found';
diff --git a/lang/en/settings.php b/lang/en/settings.php
new file mode 100644
--- /dev/null
+++ b/lang/en/settings.php
@@ -0,0 +1,6 @@
+
+ */
diff --git a/plugin.info.txt b/plugin.info.txt
new file mode 100644
--- /dev/null
+++ b/plugin.info.txt
@@ -0,0 +1,7 @@
+base davcard
+author Andreas Boehler
+email dev@aboehler.at
+date 2016-05-12
+name Addressbook PlugIn with CardDAV client support
+desc Show contact information from a CardDAV address book (needs webdavclient)
+url http://www.dokuwiki.org/plugin:davcard
diff --git a/script.js b/script.js
new file mode 100644
--- /dev/null
+++ b/script.js
@@ -0,0 +1,28 @@
+/**
+ * Initialize the DAVCard script, attaching some event handlers and triggering
+ * the initial load of the fullcalendar JS
+ */
+
+jQuery(function() {
+ jQuery('div.plugin_davcard').each(function() {
+ var $div = jQuery(this);
+ var id = $div.data('cardid');
+ var name = $div.data('name');
+ if (!id && !name) return;
+
+ jQuery.post(
+ DOKU_BASE + 'lib/exe/ajax.php',
+ {
+ call: 'plugin_davcard',
+ id: id,
+ action: 'getContact',
+ name: name,
+ sectok: JSINFO.plugin.davcard['sectok']
+ },
+ function(data)
+ {
+ $div.html(data['html']);
+ });
+
+ });
+});
diff --git a/syntax/card.php b/syntax/card.php
new file mode 100644
--- /dev/null
+++ b/syntax/card.php
@@ -0,0 +1,97 @@
+
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+class syntax_plugin_davcard_card extends DokuWiki_Syntax_Plugin {
+
+ protected $hlp = null;
+
+ // Load the helper plugin
+ public function syntax_plugin_davcard_card() {
+ $this->hlp =& plugin_load('helper', 'davcard');
+ }
+
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * What about paragraphs?
+ */
+ function getPType(){
+ return 'normal';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 165;
+ }
+
+ /**
+ * Connect pattern to lexer
+ */
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('\{\{davcard>[^}]*\}\}',$mode,'plugin_davcard_card');
+ }
+
+ /**
+ * Handle the match
+ */
+ function handle($match, $state, $pos, Doku_Handler $handler){
+ global $ID;
+ $options = trim(substr($match,10,-2));
+ $options = explode(',', $options);
+ $data = array('name' => '',
+ 'id' => '',
+ );
+ foreach($options as $option)
+ {
+ list($key, $val) = explode('=', $option);
+ $key = strtolower(trim($key));
+ $val = trim($val);
+ switch($key)
+ {
+ default:
+ $data[$key] = $val;
+ }
+ }
+ if($data['id'] === '' && $data['name'] === '')
+ {
+ msg($this->getLang('id_name_not_set'), -1);
+ }
+ return $data;
+ }
+
+ /**
+ * Create output
+ */
+ function render($format, Doku_Renderer $R, $data) {
+ if($format != 'xhtml') return false;
+
+ $R->doc .= '
';
+ $R->doc .= '
';
+ $R->doc .= $this->getLang('loading_via_ajax').'
';
+
+ }
+
+
+
+}
+
+// vim:ts=4:sw=4:et:enc=utf-8:
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+
+ private $classMapAuthoritative = false;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if ($file === null && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if ($file === null) {
+ // Remember that this class does not exist.
+ return $this->classMap[$class] = false;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) 2016 Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/sabre/vobject/lib'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,45 @@
+ $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,70 @@
+[
+ {
+ "name": "sabre/vobject",
+ "version": "3.5.2",
+ "version_normalized": "3.5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fruux/sabre-vobject.git",
+ "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/b7d6005b9f8e18bfe2b953d9847df0b3e4098441",
+ "reference": "b7d6005b9f8e18bfe2b953d9847df0b3e4098441",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=5.3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*",
+ "squizlabs/php_codesniffer": "*"
+ },
+ "time": "2016-04-24 07:05:24",
+ "bin": [
+ "bin/vobject",
+ "bin/generate_vcards"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Sabre\\VObject\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Evert Pot",
+ "email": "me@evertpot.com",
+ "homepage": "http://evertpot.com/",
+ "role": "Developer"
+ },
+ {
+ "name": "Dominik Tobschall",
+ "email": "dominik@fruux.com",
+ "homepage": "http://tobschall.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
+ "homepage": "http://sabre.io/vobject/",
+ "keywords": [
+ "VObject",
+ "iCalendar",
+ "jCal",
+ "jCard",
+ "vCard"
+ ]
+ }
+]
diff --git a/vendor/sabre/vobject/.gitignore b/vendor/sabre/vobject/.gitignore
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/.gitignore
@@ -0,0 +1,18 @@
+# Composer stuff
+vendor/
+composer.lock
+tests/cov/
+tests/temp
+
+#vim
+.*.swp
+
+#binaries
+bin/phpunit
+bin/phpcs
+
+# Development stuff
+testdata/
+
+# OS X
+.DS_Store
diff --git a/vendor/sabre/vobject/.travis.yml b/vendor/sabre/vobject/.travis.yml
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/.travis.yml
@@ -0,0 +1,19 @@
+language: php
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm
+
+matrix:
+ allow_failures:
+ - php: hhvm
+
+script:
+ - phpunit --configuration tests/phpunit.xml
+ - ./bin/phpcs -p --standard=tests/phpcs/ruleset.xml lib/
+
+
+before_script: composer install
diff --git a/vendor/sabre/vobject/ChangeLog.md b/vendor/sabre/vobject/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/ChangeLog.md
@@ -0,0 +1,617 @@
+ChangeLog
+=========
+
+3.5.2 (2016-04-24)
+-----------------
+
+* #312: Backported a fix related to iTip processing of events with timezones,
+ without a master event.
+
+
+3.5.1 (2016-04-06)
+------------------
+
+* #309: When expanding recurring events, the first event should also have a
+ `RECURRENCE-ID` property.
+* #306: iTip REPLYs to the first instance of a recurring event was not handled
+ correctly.
+
+
+3.5.0 (2016-01-11)
+------------------
+
+* This release supports PHP 7, contrary to 3.4.x versions.
+* BC Break: `Sabre\VObject\Property\Float` has been renamed to
+ `Sabre\VObject\Property\FloatValue`.
+* BC Break: `Sabre\VObject\Property\Integer` has been renamed to
+ `Sabre\VObject\Property\IntegerValue`.
+
+
+3.4.9 (2016-01-11)
+------------------
+
+* This package now specifies in composer.json that it does not support PHP 7.
+ For PHP 7, use version 3.5.x or 4.x.
+
+
+3.4.8 (2016-01-04)
+------------------
+
+* #284: When generating `CANCEL` iTip messages, we now include `DTEND`.
+ (@kewisch).
+
+
+3.4.7 (2015-09-05)
+------------------
+
+* #253: Handle `isInTimeRange` for recurring events that have 0 valid
+ instances. (@DominikTo, @migrax).
+
+
+3.4.6 (2015-08-06)
+------------------
+
+* #250: Recurring all-day events are incorrectly included in time range
+ requests when not using UTC in the time range. (@armin-hackmann)
+
+
+3.4.5 (2015-06-02)
+------------------
+
+* #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property
+ would throw an error.
+
+
+3.4.4 (2015-05-27)
+------------------
+
+* #228: Fixed a 'party crasher' bug in the iTip broker. This would break
+ scheduling in some cases.
+
+
+3.4.3 (2015-05-19)
+------------------
+
+* #219: Corrected validation of `EXDATE` properties with more than one value.
+* #212: `BYSETPOS` with values below `-1` was broken and could cause infinite
+ loops.
+* #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist)
+* #216: `ENCODING` parameter is now validated for all document types.
+* #217: Initializing vCard `DATE` objects with a PHP DateTime object will now
+ work correctly. (@thomascube)
+
+
+3.4.2 (2015-02-25)
+------------------
+
+* #210: iTip: Replying to an event without a master event was broken.
+
+
+3.4.1 (2015-02-24)
+------------------
+
+* A minor change to ensure that unittests work correctly in the sabre/dav
+ test-suite.
+
+
+3.4.0 (2015-02-23)
+------------------
+
+* #196: Made parsing recurrence rules a lot faster on big calendars.
+* Updated windows timezone mappings to latest unicode version.
+* #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan)
+* #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan)
+* #205: Improving handling of multiple `EXDATE` when processing iTip changes.
+ (@armin-hackmann)
+* #187: Fixed validator rules for `LAST-MODIFIED` properties.
+* #188: Retain floating times when generating instances using
+ `Recur\EventIterator`.
+* #203: Skip tests for timezones that are not supported on older PHP versions,
+ instead of a hard fail.
+* #204: Dealing a bit better with vCard date-time values that contained
+ milliseconds. (which is normally invalid). (@armin-hackmann)
+
+
+3.3.5 (2015-01-09)
+------------------
+
+* #168: Expanding calendars now removes objects with recurrence rules that
+ don't have a valid recurrence instance.
+* #177: SCHEDULE-STATUS should not contain a reason phrase, only a status
+ code.
+* #175: Parser can now read and skip the UTF-8 BOM.
+* #179: Added `isFloating` to `DATE-TIME` properties.
+* #179: Fixed jCal serialization of floating `DATE-TIME` properties.
+* #173: vCard converter failed for `X-ABDATE` properties that had no
+ `X-ABLABEL`.
+* #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules
+ specific for CalDAV/CardDAV servers.
+* #176: A missing `UID` is no longer an error, but a warning for the vCard
+ validator, unless `PROFILE_CARDDAV` is specified.
+
+
+3.3.4 (2014-11-19)
+------------------
+
+* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and
+ vice-versa when converting to/from vCard 4.
+* #154: It's now possible to easily select all vCard properties belonging to
+ a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann)
+* #156: Simpler way to check if a string is UTF-8. (@Hywan)
+* Unittest improvements.
+* #159: The recurrence iterator, freebusy generator and iCalendar DATE and
+ DATE-TIME properties can now all accept a reference timezone when working
+ floating times or all-day events.
+* #159: Master events will no longer get a `RECURRENCE-ID` when expanding.
+* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding.
+* #163: Added a `getTimeZone()` method to `VTIMEZONE` components.
+
+
+3.3.3 (2014-10-09)
+------------------
+
+* #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the
+ original event.
+* #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected.
+* #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is
+ set to support the inbox feature of iOS.
+* #147: Bugs related to scheduling all-day events.
+* #148: Ignore events that have attendees but no organizer.
+* #149: Avoiding logging errors during timezone detection. This is a workaround
+ for a PHP bug.
+* Support for "Line Islands Standard Time" windows timezone.
+* #154: Correctly work around vCard parameters that have a value but no name.
+
+
+3.3.2 (2014-09-19)
+------------------
+
+* Changed: iTip broker now sets RSVP status to false when replies are received.
+* #118: iTip Message now has a `getScheduleStatus()` method.
+* #119: Support for detecting 'significant changes'.
+* #120: Support for `SCHEDULE-FORCE-SEND`.
+* #121: iCal demands parameters containing the + sign to be quoted.
+* #122: Don't generate REPLY messages for events that have been cancelled.
+* #123: Added `SUMMARY` to iTip messages.
+* #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`).
+* #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`.
+* #131: RRULE that doesn't provide a single valid instance now throws an
+ exception.
+* #136: Validator rejects *all* control characters. We were missing a few.
+* #133: Splitter objects will throw exceptions when receiving incompatible
+ objects.
+* #127: Attendees who delete recurring event instances events they had already
+ declined earlier will no longer generate another reply.
+* #125: Send CANCEL messages when ORGANIZER property gets deleted.
+
+
+3.3.1 (2014-08-18)
+------------------
+
+* Changed: It's now possible to pass DateTime objects when using the magic
+ setters on properties. (`$event->DTSTART = new DateTime('now')`).
+* #111: iTip Broker does not process attendee adding events to EXDATE.
+* #112: EventIterator now sets TZID on RECURRENCE-ID.
+* #113: Timezone support during creation of iTip REPLY messages.
+* #114: VTIMEZONE is retained when generating new REQUEST objects.
+* #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip
+ broker. This improves evolution support.
+* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into
+ SCHEDULE-STATUS.
+
+
+3.3.0 (2014-08-07)
+------------------
+
+* We now use PSR-4 for the directory structure. This means that everything
+ that was used to be in the `lib/Sabre/VObject` directory is now moved to
+ `lib/`. If you use composer to load this library, you shouldn't have to do
+ anything about that though.
+* VEVENT now get populated with a DTSTAMP and UID property by default.
+* BC Break: Removed the 'includes.php' file. Use composer instead.
+* #103: Added support for processing [iTip][iTip] messages. This allows a user
+ to parse incoming iTip messages and apply the result on existing calendars,
+ or automatically generate invites/replies/cancellations based on changes that
+ a user made on objects.
+* #75, #58, #18: Fixes related to overriding the first event in recurrences.
+* Added: VCalendar::getBaseComponent to find the 'master' component in a
+ calendar.
+* #51: Support for iterating RDATE properties.
+* Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are
+ excluded events with wrong time
+
+
+3.2.4 (2014-07-14)
+------------------
+
+* Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into
+ consideration when converting between vCard 3 and 4.
+* Fixed: Issue #96. Some support for Yahoo's broken vcards.
+* Fixed: PHP 5.3 support was broken in the cli tool.
+
+
+3.2.3 (2014-06-12)
+------------------
+
+* Validator now checks if DUE and DTSTART are of the same type in VTODO, and
+ ensures that DUE is always after DTSTART.
+* Removed documentation from source repository, to http://sabre.io/vobject/
+* Expanded the vobject cli tool validation output to make it easier to find
+ issues.
+* Fixed: vobject repair. It was not working for iCalendar objects.
+
+
+3.2.2 (2014-05-07)
+------------------
+
+* Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying
+ slightly changed which caused the test to fail.
+
+
+3.2.1 (2014-05-03)
+------------------
+
+* Minor tweak to make the unittests run with the latest hhvm on travis.
+* Updated timezone definitions.
+* Updated copyright links to point to http://sabre.io/
+
+
+3.2.0 (2014-04-02)
+------------------
+
+* Now hhvm compatible!
+* The validator can now detect a _lot_ more problems. Many rules for both
+ iCalendar and vCard were added.
+* Added: bin/generate_vcards, a utility to generate random vcards for testing
+ purposes. Patches are welcome to add more data.
+* Updated: Windows timezone mapping to latest version from unicode.org
+* Changed: The timezone maps are now loaded in from external files, in
+ lib/Sabre/VObject/timezonedata.
+* Added: Fixing badly encoded URL's from google contacts vcards.
+* Fixed: Issue #68. Couldn't decode properties ending in a colon.
+* Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL
+ clause.
+* Fixed: Issue #67. BYMONTH limit on DAILY recurrences.
+* Fixed: Issue #26. Return a more descriptive error when coming across broken
+ BYDAY rules.
+* Fixed: Issue #28. Incorrect timezone detection for some timezones.
+* Fixed: Issue #70. Casting a parameter with a null value to string would fail.
+* Added: Support for rfc6715 and rfc6474.
+* Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property.
+* Added: UUIDUtil, for easily creating unique identifiers.
+* Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime.
+* Fixed: Issue #86. Don't go into an infinite loop when php errors are
+ disabled and an invalid file is read.
+
+
+3.1.4 (2014-03-30)
+------------------
+
+* Fixed: Issue #87: Several compatibility fixes related to timezone handling
+ changes in PHP 5.5.10.
+
+
+3.1.3 (2013-10-02)
+------------------
+
+* Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue
+ #56.
+* Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than
+ one newline. Thanks @Vedmak for the patch.
+* Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to
+ be inserted.
+* Added: VCardConverter removed properties that are no longer supported in vCard
+ 4.0.
+* Added: vCards with a minimum number of values (such as N), but don't have that
+ many, are now automatically padded with empty components.
+* Added: The vCard validator now also checks for a minimum number of components,
+ and has the ability to repair these.
+* Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard
+ 3.0 or 4.0.
+* Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level
+ VCalendar and VCard components.
+* Fixed: Issue 62: Parsing iCalendar parameters with no value.
+* Added: --forgiving option to vobject utility.
+* Fixed: Compound properties such as ADR were not correctly split up in vCard
+ 2.1 quoted printable-encoded properties.
+* Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks
+ @DominikTo for the patch.
+
+
+3.1.2 (2013-08-13)
+------------------
+
+* Fixed: Setting correct property group on VCard conversion
+
+
+3.1.1 (2013-08-02)
+------------------
+
+* Fixed: Issue #53. A regression in RecurrenceIterator.
+
+
+3.1.0 (2013-07-27)
+------------------
+
+* Added: bad-ass new cli debugging utility (in bin/vobject).
+* Added: jCal and jCard parser.
+* Fixed: URI properties should not escape ; and ,.
+* Fixed: VCard 4 documents now correctly use URI as a default value-type for
+ PHOTO and others. BINARY no longer exists in vCard 4.
+* Added: Utility to convert between 2.1, 3.0 and 4.0 vCards.
+* Added: You can now add() multiple parameters to a property in one call.
+* Added: Parameter::has() for easily checking if a parameter value exists.
+* Added: VCard::preferred() to find a preferred email, phone number, etc for a
+ contact.
+* Changed: All $duration properties are now public.
+* Added: A few validators for iCalendar documents.
+* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception
+ events are out of order in the iCalendar file.
+* Fixed: Issue #48. Overridden events in the recurrence iterator that were past
+ the UNTIL date were ignored.
+* Added: getDuration for DURATION values such as TRIGGER. Thanks to
+ @SimonSimCity.
+* Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's
+ more than 1. Thanks to @Vedmak.
+
+
+3.0.0 (2013-06-21)
+------------------
+
+* Fixed: includes.php file was still broken. Our tool to generate it had some
+ bugs.
+
+
+3.0.0-beta4 (2013-06-21)
+------------------------
+
+* Fixed: includes.php was no longer up to date.
+
+
+3.0.0-beta3 (2013-06-17)
+------------------------
+
+* Added: OPTION_FORGIVING now also allows slashes in property names.
+* Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999
+* Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with
+ a missing base event.
+* Fixed: jCard encoding of TIME properties.
+* Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values.
+
+
+3.0.0-beta2 (2013-06-10)
+------------------------
+
+* Fixed: Corrected includes.php file.
+* Fixed: vCard date-time parser supported extended-format dates as well.
+* Changed: Properties have been moved to an ICalendar or VCard directory.
+* Fixed: Couldn't parse vCard 3 extended format dates and times.
+* Fixed: Couldn't export jCard DATE values correctly.
+* Fixed: Recursive loop in ICalendar\DateTime property.
+
+
+3.0.0-beta1 (2013-06-07)
+------------------------
+
+* Added: jsonSerialize() for creating jCal and jCard documents.
+* Added: helper method to parse vCard dates and times.
+* Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP,
+ DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties.
+* Removed: CommaSeparatedText property. Now included into Text.
+* Fixed: Multiple parameters with the same name are now correctly encoded.
+* Fixed: Parameter values containing a comma are now enclosed in double-quotes.
+* Fixed: Iterating parameter values should now fully work as expected.
+* Fixed: Support for vCard 2.1 nameless parameters.
+* Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified
+ class names, so they are actually overridable.
+* Fixed: Updating DATE-TIME to DATE values now behaves like expected.
+
+
+3.0.0-alpha4 (2013-05-31)
+-------------------------
+
+* Added: It's now possible to send parser options to the splitter classes.
+* Added: A few tweaks to improve component and property creation.
+
+
+3.0.0-alpha3 (2013-05-13)
+-------------------------
+
+* Changed: propertyMap, valueMap and componentMap are now static properties.
+* Changed: Component::remove() will throw an exception when trying to a node
+ that's not a child of said component.
+* Added: Splitter objects are now faster, line numbers are accurately reported
+ and use less memory.
+* Added: MimeDir parser can now continue parsing with the same stream buffer.
+* Fixed: vobjectvalidate.php is operational again.
+* Fixed: \r is properly stripped in text values.
+* Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for
+ vCards 2.1.
+* Fixed: Parser assumes vCard 2.1, if no version was supplied.
+
+
+3.0.0-alpha2 (2013-05-22)
+-------------------------
+
+* Fixed: vCard URL properties were referencing a non-existant class.
+
+
+3.0.0-alpha1 (2013-05-21)
+-------------------------
+
+* Fixed: Now correctly dealing with escaping of properties. This solves the
+ problem with double-backslashes where they don't belong.
+* Added: Easy support for properties with more than one value, using setParts
+ and getParts.
+* Added: Support for broken 2.1 vCards produced by microsoft.
+* Added: Automatically decoding quoted-printable values.
+* Added: Automatically decoding base64 values.
+* Added: Decoding RFC6868 parameter values (uses ^ as an escape character).
+* Added: Fancy new MimeDir parser that can also parse streams.
+* Added: Automatically mapping many, many properties to a property-class with
+ specialized API's.
+* Added: remove() method for easily removing properties and sub-components
+ components.
+* Changed: Components, Properties and Parameters can no longer be created with
+ Component::create, Property::create and Parameter::create. They must instead
+ be created through the root component. (A VCalendar or VCard object).
+* Changed: API for DateTime properties has slightly changed.
+* Changed: the ->value property is now protected everywhere. Use getParts() and
+ getValue() instead.
+* BC Break: No support for mac newlines (\r). Never came across these anyway.
+* Added: add() method to the Property class.
+* Added: It's now possible to easy set multi-value properties as arrays.
+* Added: When setting date-time properties you can just pass PHP's DateTime
+ object.
+* Added: New components automatically get a bunch of default properties, such as
+ VERSION and CALSCALE.
+* Added: You can add new sub-components much quicker with the magic setters, and
+ add() method.
+
+
+2.1.7 (2015-01-21)
+------------------
+
+* Fixed: Issue #94, a workaround for bad escaping of ; and , in compound
+ properties. It's not a full solution, but it's an improvement for those
+ stuck in the 2.1 versions.
+
+
+2.1.6 (2014-12-10)
+------------------
+
+* Fixed: Minor change to make sure that unittests succeed on every PHP version.
+
+
+2.1.5 (2014-06-03)
+------------------
+
+* Fixed: #94: Better parameter escaping.
+* Changed: Documentation cleanups.
+
+
+2.1.4 (2014-03-30)
+------------------
+
+* Fixed: Issue #87: Several compatibility fixes related to timezone handling
+ changes in PHP 5.5.10.
+
+
+2.1.3 (2013-10-02)
+------------------
+
+* Fixed: Issue #55. \r must be stripped from property values.
+* Fixed: Issue #65. Putting quotes around parameter values that contain a colon.
+
+
+2.1.2 (2013-08-02)
+------------------
+
+* Fixed: Issue #53. A regression in RecurrenceIterator.
+
+
+2.1.1 (2013-07-27)
+------------------
+
+* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception
+ events are out of order in the iCalendar file.
+* Fixed: Issue #48. Overridden events in the recurrence iterator that were past
+ the UNTIL date were ignored.
+
+
+2.1.0 (2013-06-17)
+------------------
+
+* This version is fully backwards compatible with 2.0.\*. However, it contains a
+ few new API's that mimic the VObject 3 API. This allows it to be used a
+ 'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and
+ 1.8 can run with both the 2 and 3 versions of this library.
+* Added: Property\DateTime::hasTime().
+* Added: Property\MultiDateTime::hasTime().
+* Added: Property::getValue().
+* Added: Document class.
+* Added: Document::createComponent and Document::createProperty.
+* Added: Parameter::getValue().
+
+
+2.0.7 (2013-03-05)
+------------------
+
+* Fixed: Microsoft re-uses their magic numbers for different timezones,
+ specifically id 2 for both Sarajevo and Lisbon). A workaround was added to
+ deal with this.
+
+
+2.0.6 (2013-02-17)
+------------------
+
+* Fixed: The reader now properly parses parameters without a value.
+
+
+2.0.5 (2012-11-05)
+------------------
+
+* Fixed: The FreeBusyGenerator is now properly using the factory methods for
+ creation of components and properties.
+
+
+2.0.4 (2012-11-02)
+------------------
+
+* Added: Known Lotus Notes / Domino timezone id's.
+
+
+2.0.3 (2012-10-29)
+------------------
+
+* Added: Support for 'GMT+????' format in TZID's.
+* Added: Support for formats like SystemV/EST5EDT in TZID's.
+* Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART.
+* Added: Support for BYHOUR in FREQ=DAILY (@hollodk).
+* Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY.
+
+
+2.0.2 (2012-10-06)
+------------------
+
+* Added: includes.php file, to load the entire library in one go.
+* Fixed: A problem with determining alarm triggers for TODO's.
+
+
+2.0.1 (2012-09-22)
+------------------
+
+* Removed: Element class. It wasn't used.
+* Added: Basic validation and repair methods for broken input data.
+* Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was
+ specified.
+* Added: A cli script that can validate and automatically repair vcards and
+ iCalendar objects.
+* Added: A new 'Compound' property, that can automatically split up parts for
+ properties such as N, ADR, ORG and CATEGORIES.
+* Added: Splitter classes, that can split up large objects (such as exports)
+ into individual objects (thanks @DominikTO and @armin-hackmann).
+* Added: VFREEBUSY component, which allows easily checking wether timeslots are
+ available.
+* Added: The Reader class now has a 'FORGIVING' option, which allows it to parse
+ properties with incorrect characters in the name (at this time, it just allows
+ underscores).
+* Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard
+ any invalid lines.
+* Fixed: A bug in Windows timezone-id mappings for times created in Greenlands
+ timezone (sorry Greenlanders! I do care!).
+* Fixed: DTEND was not generated correctly for VFREEBUSY reports.
+* Fixed: Parser is at least 25% faster with real-world data.
+
+
+2.0.0 (2012-08-08)
+------------------
+
+* VObject is now a separate project from SabreDAV. See the SabreDAV changelog
+ for version information before 2.0.
+* New: VObject library now uses PHP 5.3 namespaces.
+* New: It's possible to specify lists of parameters when constructing
+ properties.
+* New: made it easier to construct the FreeBusyGenerator.
+
+[iTip]: http://tools.ietf.org/html/rfc5546
diff --git a/vendor/sabre/vobject/LICENSE b/vendor/sabre/vobject/LICENSE
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/LICENSE
@@ -0,0 +1,27 @@
+Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name Sabre nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/sabre/vobject/README.md b/vendor/sabre/vobject/README.md
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/README.md
@@ -0,0 +1,50 @@
+sabre/vobject
+=============
+
+The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545)
+and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP.
+
+The goal of the VObject library is to create a very complete library, with an easy to use API.
+
+Build status
+------------
+
+| branch | status |
+| ------ | ------ |
+| master | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=master)](https://travis-ci.org/fruux/sabre-vobject) |
+| 3.4 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.4)](https://travis-ci.org/fruux/sabre-vobject) |
+| 3.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.1)](https://travis-ci.org/fruux/sabre-vobject) |
+| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-vobject) |
+| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-vobject) |
+
+
+Installation
+------------
+
+VObject requires PHP 5.3, and should be installed using composer.
+The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website).
+
+After that, just declare the vobject dependency as follows:
+
+ "require" : {
+ "sabre/vobject" : "~3.4"
+ }
+
+Then, run `composer.phar update` and you should be good.
+
+Usage
+-----
+
+* [3.x documentation](http://sabre.io/vobject/usage/)
+* [2.x documentation](http://sabre.io/vobject/usage_2/)
+* [Migrating from 2.x to 3.x](http://sabre.io/vobject/upgrade/)
+
+Support
+-------
+
+Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions.
+
+Made at fruux
+-------------
+
+This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
diff --git a/vendor/sabre/vobject/bin/bench.php b/vendor/sabre/vobject/bin/bench.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/bin/bench.php
@@ -0,0 +1,12 @@
+#!/usr/bin/env php
+xpath('//mapZone') as $mapZone) {
+
+ $from = (string)$mapZone['other'];
+ $to = (string)$mapZone['type'];
+
+ list($to) = explode(' ', $to, 2);
+
+ if (!isset($map[$from])) {
+ $map[$from] = $to;
+ }
+
+}
+
+ksort($map);
+echo "Writing to: $outputFile\n";
+
+$f = fopen($outputFile,'w');
+fwrite($f, " testdata.vcf
+
+HI;
+
+ fwrite(STDERR, $help);
+ exit(2);
+}
+
+$count = (int)$argv[1];
+if ($count < 1) {
+ fwrite(STDERR, "Count must be at least 1\n");
+ exit(2);
+}
+
+fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n");
+fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n");
+
+/**
+ * The following list is just some random data we compiled from various
+ * sources online.
+ *
+ * Very little thought went into compiling this list, and certainly nothing
+ * political or ethical.
+ *
+ * We would _love_ more additions to this to add more variation to this list.
+ *
+ * Send us PR's and don't be shy adding your own first and last name for fun.
+ */
+
+$sets = array(
+ "nl" => array(
+ "country" => "Netherlands",
+ "boys" => array(
+ "Anno",
+ "Bram",
+ "Daan",
+ "Evert",
+ "Finn",
+ "Jayden",
+ "Jens",
+ "Jesse",
+ "Levi",
+ "Lucas",
+ "Luuk",
+ "Milan",
+ "René",
+ "Sem",
+ "Sibrand",
+ "Willem",
+ ),
+ "girls" => array(
+ "Celia",
+ "Emma",
+ "Fenna",
+ "Geke",
+ "Inge",
+ "Julia",
+ "Lisa",
+ "Lotte",
+ "Mila",
+ "Sara",
+ "Sophie",
+ "Tess",
+ "Zoë",
+ ),
+ "last" => array(
+ "Bakker",
+ "Bos",
+ "De Boer",
+ "De Groot",
+ "De Jong",
+ "De Vries",
+ "Jansen",
+ "Janssen",
+ "Meyer",
+ "Mulder",
+ "Peters",
+ "Smit",
+ "Van Dijk",
+ "Van den Berg",
+ "Visser",
+ "Vos",
+ ),
+ ),
+ "us" => array(
+ "country" => "United States",
+ "boys" => array(
+ "Aiden",
+ "Alexander",
+ "Charles",
+ "David",
+ "Ethan",
+ "Jacob",
+ "James",
+ "Jayden",
+ "John",
+ "Joseph",
+ "Liam",
+ "Mason",
+ "Michael",
+ "Noah",
+ "Richard",
+ "Robert",
+ "Thomas",
+ "William",
+ ),
+ "girls" => array(
+ "Ava",
+ "Barbara",
+ "Chloe",
+ "Dorothy",
+ "Elizabeth",
+ "Emily",
+ "Emma",
+ "Isabella",
+ "Jennifer",
+ "Lily",
+ "Linda",
+ "Margaret",
+ "Maria",
+ "Mary",
+ "Mia",
+ "Olivia",
+ "Patricia",
+ "Roxy",
+ "Sophia",
+ "Susan",
+ "Zoe",
+ ),
+ "last" => array(
+ "Smith",
+ "Johnson",
+ "Williams",
+ "Jones",
+ "Brown",
+ "Davis",
+ "Miller",
+ "Wilson",
+ "Moore",
+ "Taylor",
+ "Anderson",
+ "Thomas",
+ "Jackson",
+ "White",
+ "Harris",
+ "Martin",
+ "Thompson",
+ "Garcia",
+ "Martinez",
+ "Robinson",
+ ),
+ ),
+);
+
+$current = 0;
+
+$r = function($arr) {
+
+ return $arr[mt_rand(0,count($arr)-1)];
+
+};
+
+$bdayStart = strtotime('-85 years');
+$bdayEnd = strtotime('-20 years');
+
+while($current < $count) {
+
+ $current++;
+ fwrite(STDERR, "\033[100D$current/$count");
+
+ $country = array_rand($sets);
+ $gender = mt_rand(0,1)?'girls':'boys';
+
+ $vcard = new Component\VCard(array(
+ 'VERSION' => '4.0',
+ 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']),
+ 'UID' => UUIDUtil::getUUID(),
+ ));
+
+ $bdayRatio = mt_rand(0,9);
+
+ if($bdayRatio < 2) {
+ // 20% has a birthday property with a full date
+ $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
+ $vcard->add('BDAY', $dt->format('Ymd'));
+
+ } elseif ($bdayRatio < 3) {
+ // 10% we only know the month and date of
+ $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
+ $vcard->add('BDAY', '--' . $dt->format('md'));
+ }
+ if ($result = $vcard->validate()) {
+ ob_start();
+ echo "\nWe produced an invalid vcard somehow!\n";
+ foreach($result as $message) {
+ echo " " . $message['message'] . "\n";
+ }
+ fwrite(STDERR, ob_get_clean());
+ }
+ echo $vcard->serialize();
+
+}
+
+fwrite(STDERR,"\nDone.\n");
diff --git a/vendor/sabre/vobject/bin/generateicalendardata.php b/vendor/sabre/vobject/bin/generateicalendardata.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/bin/generateicalendardata.php
@@ -0,0 +1,91 @@
+#!/usr/bin/env php
+version = '2.0';
+$calendar->calscale = 'GREGORIAN';
+
+$ii=0;
+
+while($ii < $events) {
+
+ $ii++;
+
+ $event = VObject\Component::create('VEVENT');
+ $event->DTSTART = 'bla';
+ $event->SUMMARY = 'Event #' . $ii;
+ $event->UID = md5(microtime(true));
+
+ $doctorRandom = mt_rand(1,1000);
+
+ switch($doctorRandom) {
+ // All-day event
+ case 1 :
+ $event->DTEND = 'bla';
+ $dtStart = clone $currentDate;
+ $dtEnd = clone $currentDate;
+ $dtEnd->modify('+' . mt_rand(1,3) . ' days');
+ $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::DATE);
+ $event->DTEND->setDateTime($dtEnd, VObject\Property\DateTime::DATE);
+ break;
+ case 2 :
+ $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1,10);
+ // No break intentional
+ default :
+ $dtStart = clone $currentDate;
+ $dtStart->setTime(mt_rand(1,23), mt_rand(0,59), mt_rand(0,59));
+ $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::UTC);
+ $event->DURATION = 'PT'.mt_rand(1,3).'H';
+ break;
+
+ }
+
+ $calendar->add($event);
+ $currentDate->modify('+ ' . mt_rand(0,3) . ' days');
+
+}
+fwrite(STDERR, "Validating\n");
+
+$result = $calendar->validate();
+if ($result) {
+ fwrite(STDERR, "Errors!\n");
+ fwrite(STDERR, print_r($result,true));
+ die(-1);
+}
+
+fwrite(STDERR, "Serializing this beast\n");
+
+echo $calendar->serialize();
+
+fwrite(STDERR, "done.\n");
+
diff --git a/vendor/sabre/vobject/bin/rrulebench.php b/vendor/sabre/vobject/bin/rrulebench.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/bin/rrulebench.php
@@ -0,0 +1,32 @@
+parse->start();
+
+echo "Parsing.\n";
+$vobj = Sabre\VObject\Reader::read(fopen($inputFile,'r'));
+
+$bench->parse->stop();
+
+echo "Expanding.\n";
+$bench->expand->start();
+
+$vobj->expand(new DateTime($startDate), new DateTime($endDate));
+
+$bench->expand->stop();
+
+echo $bench,"\n";
diff --git a/vendor/sabre/vobject/bin/vobject b/vendor/sabre/vobject/bin/vobject
new file mode 100755
--- /dev/null
+++ b/vendor/sabre/vobject/bin/vobject
@@ -0,0 +1,27 @@
+#!/usr/bin/env php
+main($argv));
+
diff --git a/vendor/sabre/vobject/composer.json b/vendor/sabre/vobject/composer.json
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "sabre/vobject",
+ "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
+ "keywords" : [ "VObject", "iCalendar", "vCard", "jCard", "jCal" ],
+ "homepage" : "http://sabre.io/vobject/",
+ "license" : "BSD-3-Clause",
+ "require" : {
+ "php" : ">=5.3.1",
+ "ext-mbstring" : "*"
+ },
+ "require-dev" : {
+ "phpunit/phpunit" : "*",
+ "squizlabs/php_codesniffer": "*"
+ },
+ "authors" : [
+ {
+ "name" : "Evert Pot",
+ "email" : "me@evertpot.com",
+ "homepage" : "http://evertpot.com/",
+ "role" : "Developer"
+ },
+ {
+ "name" : "Dominik Tobschall",
+ "email" : "dominik@fruux.com",
+ "homepage" : "http://tobschall.de/",
+ "role" : "Developer"
+ }
+ ],
+ "support" : {
+ "forum" : "https://groups.google.com/group/sabredav-discuss",
+ "source" : "https://github.com/fruux/sabre-vobject"
+ },
+ "autoload" : {
+ "psr-4" : {
+ "Sabre\\VObject\\" : "lib/"
+ }
+ },
+ "bin" : [
+ "bin/vobject",
+ "bin/generate_vcards"
+ ],
+ "extra" : {
+ "branch-alias" : {
+ "dev-master" : "3.2.x-dev"
+ }
+ },
+ "config" : {
+ "bin-dir" : "bin"
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Cli.php b/vendor/sabre/vobject/lib/Cli.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Cli.php
@@ -0,0 +1,761 @@
+stderr) {
+ $this->stderr = fopen('php://stderr', 'w');
+ }
+ if (!$this->stdout) {
+ $this->stdout = fopen('php://stdout', 'w');
+ }
+ if (!$this->stdin) {
+ $this->stdin = fopen('php://stdin', 'r');
+ }
+
+ // @codeCoverageIgnoreEnd
+
+
+ try {
+
+ list($options, $positional) = $this->parseArguments($argv);
+
+ if (isset($options['q'])) {
+ $this->quiet = true;
+ }
+ $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
+
+ foreach($options as $name=>$value) {
+
+ switch($name) {
+
+ case 'q' :
+ // Already handled earlier.
+ break;
+ case 'h' :
+ case 'help' :
+ $this->showHelp();
+ return 0;
+ break;
+ case 'format' :
+ switch($value) {
+
+ // jcard/jcal documents
+ case 'jcard' :
+ case 'jcal' :
+
+ // specific document versions
+ case 'vcard21' :
+ case 'vcard30' :
+ case 'vcard40' :
+ case 'icalendar20' :
+
+ // specific formats
+ case 'json' :
+ case 'mimedir' :
+
+ // icalendar/vcad
+ case 'icalendar' :
+ case 'vcard' :
+ $this->format = $value;
+ break;
+
+ default :
+ throw new InvalidArgumentException('Unknown format: ' . $value);
+
+ }
+ break;
+ case 'pretty' :
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+ $this->pretty = true;
+ }
+ break;
+ case 'forgiving' :
+ $this->forgiving = true;
+ break;
+ case 'inputformat' :
+ switch($value) {
+ // json formats
+ case 'jcard' :
+ case 'jcal' :
+ case 'json' :
+ $this->inputFormat = 'json';
+ break;
+
+ // mimedir formats
+ case 'mimedir' :
+ case 'icalendar' :
+ case 'vcard' :
+ case 'vcard21' :
+ case 'vcard30' :
+ case 'vcard40' :
+ case 'icalendar20' :
+
+ $this->inputFormat = 'mimedir';
+ break;
+
+ default :
+ throw new InvalidArgumentException('Unknown format: ' . $value);
+
+ }
+ break;
+ default :
+ throw new InvalidArgumentException('Unknown option: ' . $name);
+
+ }
+
+ }
+
+ if (count($positional) === 0) {
+ $this->showHelp();
+ return 1;
+ }
+
+ if (count($positional) === 1) {
+ throw new InvalidArgumentException('Inputfile is a required argument');
+ }
+
+ if (count($positional) > 3) {
+ throw new InvalidArgumentException('Too many arguments');
+ }
+
+ if (!in_array($positional[0], array('validate','repair','convert','color'))) {
+ throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
+ }
+
+ } catch (InvalidArgumentException $e) {
+ $this->showHelp();
+ $this->log('Error: ' . $e->getMessage(), 'red');
+ return 1;
+ }
+
+ $command = $positional[0];
+
+ $this->inputPath = $positional[1];
+ $this->outputPath = isset($positional[2])?$positional[2]:'-';
+
+ if ($this->outputPath !== '-') {
+ $this->stdout = fopen($this->outputPath, 'w');
+ }
+
+ if (!$this->inputFormat) {
+ if (substr($this->inputPath, -5)==='.json') {
+ $this->inputFormat = 'json';
+ } else {
+ $this->inputFormat = 'mimedir';
+ }
+ }
+ if (!$this->format) {
+ if (substr($this->outputPath,-5)==='.json') {
+ $this->format = 'json';
+ } else {
+ $this->format = 'mimedir';
+ }
+ }
+
+
+ $realCode = 0;
+
+ try {
+
+ while($input = $this->readInput()) {
+
+ $returnCode = $this->$command($input);
+ if ($returnCode!==0) $realCode = $returnCode;
+
+ }
+
+ } catch (EofException $e) {
+ // end of file
+ } catch (\Exception $e) {
+ $this->log('Error: ' . $e->getMessage(),'red');
+ return 2;
+ }
+
+ return $realCode;
+
+ }
+
+ /**
+ * Shows the help message.
+ *
+ * @return void
+ */
+ protected function showHelp() {
+
+ $this->log('Usage:', 'yellow');
+ $this->log(" vobject [options] command [arguments]");
+ $this->log('');
+ $this->log('Options:', 'yellow');
+ $this->log($this->colorize('green', ' -q ') . "Don't output anything.");
+ $this->log($this->colorize('green', ' -help -h ') . "Display this help message.");
+ $this->log($this->colorize('green', ' --format ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
+ $this->log($this->colorize('green', ' --forgiving ') . "Makes the parser less strict.");
+ $this->log(" vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
+ $this->log($this->colorize('green', ' --inputformat ') . "If the input format cannot be guessed from the extension, it");
+ $this->log(" must be specified here.");
+ // Only PHP 5.4 and up
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+ $this->log($this->colorize('green', ' --pretty ') . "json pretty-print.");
+ }
+ $this->log('');
+ $this->log('Commands:', 'yellow');
+ $this->log($this->colorize('green', ' validate') . ' source_file Validates a file for correctness.');
+ $this->log($this->colorize('green', ' repair') . ' source_file [output_file] Repairs a file.');
+ $this->log($this->colorize('green', ' convert') . ' source_file [output_file] Converts a file.');
+ $this->log($this->colorize('green', ' color') . ' source_file Colorize a file, useful for debbugging.');
+ $this->log(
+ <<log('Examples:', 'yellow');
+ $this->log(' vobject convert contact.vcf contact.json');
+ $this->log(' vobject convert --format=vcard40 old.vcf new.vcf');
+ $this->log(' vobject convert --inputformat=json --format=mimedir - -');
+ $this->log(' vobject color calendar.ics');
+ $this->log('');
+ $this->log('https://github.com/fruux/sabre-vobject','purple');
+
+ }
+
+ /**
+ * Validates a VObject file
+ *
+ * @param Component $vObj
+ * @return int
+ */
+ protected function validate($vObj) {
+
+ $returnCode = 0;
+
+ switch($vObj->name) {
+ case 'VCALENDAR' :
+ $this->log("iCalendar: " . (string)$vObj->VERSION);
+ break;
+ case 'VCARD' :
+ $this->log("vCard: " . (string)$vObj->VERSION);
+ break;
+ }
+
+ $warnings = $vObj->validate();
+ if (!count($warnings)) {
+ $this->log(" No warnings!");
+ } else {
+
+ $levels = array(
+ 1 => 'REPAIRED',
+ 2 => 'WARNING',
+ 3 => 'ERROR',
+ );
+ $returnCode = 2;
+ foreach($warnings as $warn) {
+
+ $extra = '';
+ if ($warn['node'] instanceof Property) {
+ $extra = ' (property: "' . $warn['node']->name . '")';
+ }
+ $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
+
+ }
+
+ }
+
+ return $returnCode;
+
+ }
+
+ /**
+ * Repairs a VObject file
+ *
+ * @param Component $vObj
+ * @return int
+ */
+ protected function repair($vObj) {
+
+ $returnCode = 0;
+
+ switch($vObj->name) {
+ case 'VCALENDAR' :
+ $this->log("iCalendar: " . (string)$vObj->VERSION);
+ break;
+ case 'VCARD' :
+ $this->log("vCard: " . (string)$vObj->VERSION);
+ break;
+ }
+
+ $warnings = $vObj->validate(Node::REPAIR);
+ if (!count($warnings)) {
+ $this->log(" No warnings!");
+ } else {
+
+ $levels = array(
+ 1 => 'REPAIRED',
+ 2 => 'WARNING',
+ 3 => 'ERROR',
+ );
+ $returnCode = 2;
+ foreach($warnings as $warn) {
+
+ $extra = '';
+ if ($warn['node'] instanceof Property) {
+ $extra = ' (property: "' . $warn['node']->name . '")';
+ }
+ $this->log(" [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
+
+ }
+
+ }
+ fwrite($this->stdout, $vObj->serialize());
+
+ return $returnCode;
+
+ }
+
+ /**
+ * Converts a vObject file to a new format.
+ *
+ * @param Component $vObj
+ * @return int
+ */
+ protected function convert($vObj) {
+
+ $json = false;
+ $convertVersion = null;
+ $forceInput = null;
+
+ switch($this->format) {
+ case 'json' :
+ $json = true;
+ if ($vObj->name === 'VCARD') {
+ $convertVersion = Document::VCARD40;
+ }
+ break;
+ case 'jcard' :
+ $json = true;
+ $forceInput = 'VCARD';
+ $convertVersion = Document::VCARD40;
+ break;
+ case 'jcal' :
+ $json = true;
+ $forceInput = 'VCALENDAR';
+ break;
+ case 'mimedir' :
+ case 'icalendar' :
+ case 'icalendar20' :
+ case 'vcard' :
+ break;
+ case 'vcard21' :
+ $convertVersion = Document::VCARD21;
+ break;
+ case 'vcard30' :
+ $convertVersion = Document::VCARD30;
+ break;
+ case 'vcard40' :
+ $convertVersion = Document::VCARD40;
+ break;
+
+ }
+
+ if ($forceInput && $vObj->name !== $forceInput) {
+ throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
+ }
+ if ($convertVersion) {
+ $vObj = $vObj->convert($convertVersion);
+ }
+ if ($json) {
+ $jsonOptions = 0;
+ if ($this->pretty) {
+ $jsonOptions = JSON_PRETTY_PRINT;
+ }
+ fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
+ } else {
+ fwrite($this->stdout, $vObj->serialize());
+ }
+
+ return 0;
+
+ }
+
+ /**
+ * Colorizes a file
+ *
+ * @param Component $vObj
+ * @return int
+ */
+ protected function color($vObj) {
+
+ fwrite($this->stdout, $this->serializeComponent($vObj));
+
+ }
+
+ /**
+ * Returns an ansi color string for a color name.
+ *
+ * @param string $color
+ * @return string
+ */
+ protected function colorize($color, $str, $resetTo = 'default') {
+
+ $colors = array(
+ 'cyan' => '1;36',
+ 'red' => '1;31',
+ 'yellow' => '1;33',
+ 'blue' => '0;34',
+ 'green' => '0;32',
+ 'default' => '0',
+ 'purple' => '0;35',
+ );
+ return "\033[" . $colors[$color] . 'm' . $str . "\033[".$colors[$resetTo]."m";
+
+ }
+
+ /**
+ * Writes out a string in specific color.
+ *
+ * @param string $color
+ * @param string $str
+ * @return void
+ */
+ protected function cWrite($color, $str) {
+
+ fwrite($this->stdout, $this->colorize($color, $str));
+
+ }
+
+ protected function serializeComponent(Component $vObj) {
+
+ $this->cWrite('cyan', 'BEGIN');
+ $this->cWrite('red', ':');
+ $this->cWrite('yellow', $vObj->name . "\n");
+
+ /**
+ * Gives a component a 'score' for sorting purposes.
+ *
+ * This is solely used by the childrenSort method.
+ *
+ * A higher score means the item will be lower in the list.
+ * To avoid score collisions, each "score category" has a reasonable
+ * space to accomodate elements. The $key is added to the $score to
+ * preserve the original relative order of elements.
+ *
+ * @param int $key
+ * @param array $array
+ * @return int
+ */
+ $sortScore = function($key, $array) {
+
+ if ($array[$key] instanceof Component) {
+
+ // We want to encode VTIMEZONE first, this is a personal
+ // preference.
+ if ($array[$key]->name === 'VTIMEZONE') {
+ $score=300000000;
+ return $score+$key;
+ } else {
+ $score=400000000;
+ return $score+$key;
+ }
+ } else {
+ // Properties get encoded first
+ // VCARD version 4.0 wants the VERSION property to appear first
+ if ($array[$key] instanceof Property) {
+ if ($array[$key]->name === 'VERSION') {
+ $score=100000000;
+ return $score+$key;
+ } else {
+ // All other properties
+ $score=200000000;
+ return $score+$key;
+ }
+ }
+ }
+
+ };
+
+ $tmp = $vObj->children;
+ uksort(
+ $vObj->children,
+ function($a, $b) use ($sortScore, $tmp) {
+
+ $sA = $sortScore($a, $tmp);
+ $sB = $sortScore($b, $tmp);
+
+ return $sA - $sB;
+
+ }
+ );
+
+ foreach($vObj->children as $child) {
+ if ($child instanceof Component) {
+ $this->serializeComponent($child);
+ } else {
+ $this->serializeProperty($child);
+ }
+ }
+
+ $this->cWrite('cyan', 'END');
+ $this->cWrite('red', ':');
+ $this->cWrite('yellow', $vObj->name . "\n");
+
+ }
+
+ /**
+ * Colorizes a property.
+ *
+ * @param Property $property
+ * @return void
+ */
+ protected function serializeProperty(Property $property) {
+
+ if ($property->group) {
+ $this->cWrite('default', $property->group);
+ $this->cWrite('red', '.');
+ }
+
+ $str = '';
+ $this->cWrite('yellow', $property->name);
+
+ foreach($property->parameters as $param) {
+
+ $this->cWrite('red',';');
+ $this->cWrite('blue', $param->serialize());
+
+ }
+ $this->cWrite('red',':');
+
+ if ($property instanceof Property\Binary) {
+
+ $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
+
+ } else {
+
+ $parts = $property->getParts();
+ $first1 = true;
+ // Looping through property values
+ foreach($parts as $part) {
+ if ($first1) {
+ $first1 = false;
+ } else {
+ $this->cWrite('red', $property->delimiter);
+ }
+ $first2 = true;
+ // Looping through property sub-values
+ foreach((array)$part as $subPart) {
+ if ($first2) {
+ $first2 = false;
+ } else {
+ // The sub-value delimiter is always comma
+ $this->cWrite('red', ',');
+ }
+
+ $subPart = strtr(
+ $subPart,
+ array(
+ '\\' => $this->colorize('purple', '\\\\', 'green'),
+ ';' => $this->colorize('purple', '\;', 'green'),
+ ',' => $this->colorize('purple', '\,', 'green'),
+ "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
+ "\r" => "",
+ )
+ );
+
+ $this->cWrite('green', $subPart);
+ }
+ }
+
+ }
+ $this->cWrite("default", "\n");
+
+ }
+
+ /**
+ * Parses the list of arguments.
+ *
+ * @param array $argv
+ * @return void
+ */
+ protected function parseArguments(array $argv) {
+
+ $positional = array();
+ $options = array();
+
+ for($ii=0; $ii < count($argv); $ii++) {
+
+ // Skipping the first argument.
+ if ($ii===0) continue;
+
+ $v = $argv[$ii];
+
+ if (substr($v,0,2)==='--') {
+ // This is a long-form option.
+ $optionName = substr($v,2);
+ $optionValue = true;
+ if (strpos($optionName,'=')) {
+ list($optionName, $optionValue) = explode('=', $optionName);
+ }
+ $options[$optionName] = $optionValue;
+ } elseif (substr($v,0,1) === '-' && strlen($v)>1) {
+ // This is a short-form option.
+ foreach(str_split(substr($v,1)) as $option) {
+ $options[$option] = true;
+ }
+
+ } else {
+
+ $positional[] = $v;
+
+ }
+
+ }
+
+ return array($options, $positional);
+
+ }
+
+ protected $parser;
+
+ /**
+ * Reads the input file
+ *
+ * @return Component
+ */
+ protected function readInput() {
+
+ if (!$this->parser) {
+ if ($this->inputPath!=='-') {
+ $this->stdin = fopen($this->inputPath,'r');
+ }
+
+ if ($this->inputFormat === 'mimedir') {
+ $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
+ } else {
+ $this->parser = new Parser\Json($this->stdin, ($this->forgiving?Reader::OPTION_FORGIVING:0));
+ }
+ }
+
+ return $this->parser->parse();
+
+ }
+
+ /**
+ * Sends a message to STDERR.
+ *
+ * @param string $msg
+ * @return void
+ */
+ protected function log($msg, $color = 'default') {
+
+ if (!$this->quiet) {
+ if ($color!=='default') {
+ $msg = $this->colorize($color, $msg);
+ }
+ fwrite($this->stderr, $msg . "\n");
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Component.php b/vendor/sabre/vobject/lib/Component.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component.php
@@ -0,0 +1,595 @@
+value syntax, in which case
+ * properties will automatically be created, or you can just pass a list of
+ * Component and Property object.
+ *
+ * By default, a set of sensible values will be added to the component. For
+ * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
+ * ensure that this does not happen, set $defaults to false.
+ *
+ * @param Document $root
+ * @param string $name such as VCALENDAR, VEVENT.
+ * @param array $children
+ * @param bool $defaults
+ * @return void
+ */
+ function __construct(Document $root, $name, array $children = array(), $defaults = true) {
+
+ $this->name = strtoupper($name);
+ $this->root = $root;
+
+ if ($defaults) {
+ // This is a terribly convoluted way to do this, but this ensures
+ // that the order of properties as they are specified in both
+ // defaults and the childrens list, are inserted in the object in a
+ // natural way.
+ $list = $this->getDefaults();
+ $nodes = array();
+ foreach($children as $key=>$value) {
+ if ($value instanceof Node) {
+ if (isset($list[$value->name])) {
+ unset($list[$value->name]);
+ }
+ $nodes[] = $value;
+ } else {
+ $list[$key] = $value;
+ }
+ }
+ foreach($list as $key=>$value) {
+ $this->add($key, $value);
+ }
+ foreach($nodes as $node) {
+ $this->add($node);
+ }
+ } else {
+ foreach($children as $k=>$child) {
+ if ($child instanceof Node) {
+
+ // Component or Property
+ $this->add($child);
+ } else {
+
+ // Property key=>value
+ $this->add($k, $child);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Adds a new property or component, and returns the new item.
+ *
+ * This method has 3 possible signatures:
+ *
+ * add(Component $comp) // Adds a new component
+ * add(Property $prop) // Adds a new property
+ * add($name, $value, array $parameters = array()) // Adds a new property
+ * add($name, array $children = array()) // Adds a new component
+ * by name.
+ *
+ * @return Node
+ */
+ function add($a1, $a2 = null, $a3 = null) {
+
+ if ($a1 instanceof Node) {
+ if (!is_null($a2)) {
+ throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
+ }
+ $a1->parent = $this;
+ $this->children[] = $a1;
+
+ return $a1;
+
+ } elseif(is_string($a1)) {
+
+ $item = $this->root->create($a1, $a2, $a3);
+ $item->parent = $this;
+ $this->children[] = $item;
+
+ return $item;
+
+ } else {
+
+ throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');
+
+ }
+
+ }
+
+ /**
+ * This method removes a component or property from this component.
+ *
+ * You can either specify the item by name (like DTSTART), in which case
+ * all properties/components with that name will be removed, or you can
+ * pass an instance of a property or component, in which case only that
+ * exact item will be removed.
+ *
+ * The removed item will be returned. In case there were more than 1 items
+ * removed, only the last one will be returned.
+ *
+ * @param mixed $item
+ * @return void
+ */
+ function remove($item) {
+
+ if (is_string($item)) {
+ $children = $this->select($item);
+ foreach($children as $k=>$child) {
+ unset($this->children[$k]);
+ }
+ return $child;
+ } else {
+ foreach($this->children as $k => $child) {
+ if ($child===$item) {
+ unset($this->children[$k]);
+ return $child;
+ }
+ }
+
+ throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');
+
+ }
+
+ }
+
+ /**
+ * Returns an iterable list of children
+ *
+ * @return array
+ */
+ function children() {
+
+ return $this->children;
+
+ }
+
+ /**
+ * This method only returns a list of sub-components. Properties are
+ * ignored.
+ *
+ * @return array
+ */
+ function getComponents() {
+
+ $result = array();
+ foreach($this->children as $child) {
+ if ($child instanceof Component) {
+ $result[] = $child;
+ }
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * Returns an array with elements that match the specified name.
+ *
+ * This function is also aware of MIME-Directory groups (as they appear in
+ * vcards). This means that if a property is grouped as "HOME.EMAIL", it
+ * will also be returned when searching for just "EMAIL". If you want to
+ * search for a property in a specific group, you can select on the entire
+ * string ("HOME.EMAIL"). If you want to search on a specific property that
+ * has not been assigned a group, specify ".EMAIL".
+ *
+ * Keys are retained from the 'children' array, which may be confusing in
+ * certain cases.
+ *
+ * @param string $name
+ * @return array
+ */
+ function select($name) {
+
+ $group = null;
+ $name = strtoupper($name);
+ if (strpos($name,'.')!==false) {
+ list($group,$name) = explode('.', $name, 2);
+ }
+
+ $result = array();
+ foreach($this->children as $key=>$child) {
+
+ if (
+ (
+ strtoupper($child->name) === $name
+ && (is_null($group) || ( $child instanceof Property && strtoupper($child->group) === $group))
+ )
+ ||
+ (
+ $name === '' && $child instanceof Property && strtoupper($child->group) === $group
+ )
+ ) {
+
+ $result[$key] = $child;
+
+ }
+ }
+
+ reset($result);
+ return $result;
+
+ }
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ function serialize() {
+
+ $str = "BEGIN:" . $this->name . "\r\n";
+
+ /**
+ * Gives a component a 'score' for sorting purposes.
+ *
+ * This is solely used by the childrenSort method.
+ *
+ * A higher score means the item will be lower in the list.
+ * To avoid score collisions, each "score category" has a reasonable
+ * space to accomodate elements. The $key is added to the $score to
+ * preserve the original relative order of elements.
+ *
+ * @param int $key
+ * @param array $array
+ * @return int
+ */
+ $sortScore = function($key, $array) {
+
+ if ($array[$key] instanceof Component) {
+
+ // We want to encode VTIMEZONE first, this is a personal
+ // preference.
+ if ($array[$key]->name === 'VTIMEZONE') {
+ $score=300000000;
+ return $score+$key;
+ } else {
+ $score=400000000;
+ return $score+$key;
+ }
+ } else {
+ // Properties get encoded first
+ // VCARD version 4.0 wants the VERSION property to appear first
+ if ($array[$key] instanceof Property) {
+ if ($array[$key]->name === 'VERSION') {
+ $score=100000000;
+ return $score+$key;
+ } else {
+ // All other properties
+ $score=200000000;
+ return $score+$key;
+ }
+ }
+ }
+
+ };
+
+ $tmp = $this->children;
+ uksort(
+ $this->children,
+ function($a, $b) use ($sortScore, $tmp) {
+
+ $sA = $sortScore($a, $tmp);
+ $sB = $sortScore($b, $tmp);
+
+ return $sA - $sB;
+
+ }
+ );
+
+ foreach($this->children as $child) $str.=$child->serialize();
+ $str.= "END:" . $this->name . "\r\n";
+
+ return $str;
+
+ }
+
+ /**
+ * This method returns an array, with the representation as it should be
+ * encoded in json. This is used to create jCard or jCal documents.
+ *
+ * @return array
+ */
+ function jsonSerialize() {
+
+ $components = array();
+ $properties = array();
+
+ foreach($this->children as $child) {
+ if ($child instanceof Component) {
+ $components[] = $child->jsonSerialize();
+ } else {
+ $properties[] = $child->jsonSerialize();
+ }
+ }
+
+ return array(
+ strtolower($this->name),
+ $properties,
+ $components
+ );
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return array();
+
+ }
+
+ /* Magic property accessors {{{ */
+
+ /**
+ * Using 'get' you will either get a property or component.
+ *
+ * If there were no child-elements found with the specified name,
+ * null is returned.
+ *
+ * To use this, this may look something like this:
+ *
+ * $event = $calendar->VEVENT;
+ *
+ * @param string $name
+ * @return Property
+ */
+ function __get($name) {
+
+ $matches = $this->select($name);
+ if (count($matches)===0) {
+ return null;
+ } else {
+ $firstMatch = current($matches);
+ /** @var $firstMatch Property */
+ $firstMatch->setIterator(new ElementList(array_values($matches)));
+ return $firstMatch;
+ }
+
+ }
+
+ /**
+ * This method checks if a sub-element with the specified name exists.
+ *
+ * @param string $name
+ * @return bool
+ */
+ function __isset($name) {
+
+ $matches = $this->select($name);
+ return count($matches)>0;
+
+ }
+
+ /**
+ * Using the setter method you can add properties or subcomponents
+ *
+ * You can either pass a Component, Property
+ * object, or a string to automatically create a Property.
+ *
+ * If the item already exists, it will be removed. If you want to add
+ * a new item with the same name, always use the add() method.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ function __set($name, $value) {
+
+ $matches = $this->select($name);
+ $overWrite = count($matches)?key($matches):null;
+
+ if ($value instanceof Component || $value instanceof Property) {
+ $value->parent = $this;
+ if (!is_null($overWrite)) {
+ $this->children[$overWrite] = $value;
+ } else {
+ $this->children[] = $value;
+ }
+ } else {
+ $property = $this->root->create($name,$value);
+ $property->parent = $this;
+ if (!is_null($overWrite)) {
+ $this->children[$overWrite] = $property;
+ } else {
+ $this->children[] = $property;
+ }
+ }
+ }
+
+ /**
+ * Removes all properties and components within this component with the
+ * specified name.
+ *
+ * @param string $name
+ * @return void
+ */
+ function __unset($name) {
+
+ $matches = $this->select($name);
+ foreach($matches as $k=>$child) {
+
+ unset($this->children[$k]);
+ $child->parent = null;
+
+ }
+
+ }
+
+ /* }}} */
+
+ /**
+ * This method is automatically called when the object is cloned.
+ * Specifically, this will ensure all child elements are also cloned.
+ *
+ * @return void
+ */
+ function __clone() {
+
+ foreach($this->children as $key=>$child) {
+ $this->children[$key] = clone $child;
+ $this->children[$key]->parent = $this;
+ }
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * It is also possible to specify defaults and severity levels for
+ * violating the rule.
+ *
+ * See the VEVENT implementation for getValidationRules for a more complex
+ * example.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return array();
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $rules = $this->getValidationRules();
+ $defaults = $this->getDefaults();
+
+ $propertyCounters = array();
+
+ $messages = array();
+
+ foreach($this->children as $child) {
+ $name = strtoupper($child->name);
+ if (!isset($propertyCounters[$name])) {
+ $propertyCounters[$name] = 1;
+ } else {
+ $propertyCounters[$name]++;
+ }
+ $messages = array_merge($messages, $child->validate($options));
+ }
+
+ foreach($rules as $propName => $rule) {
+
+ switch($rule) {
+ case '0' :
+ if (isset($propertyCounters[$propName])) {
+ $messages[] = array(
+ 'level' => 3,
+ 'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
+ 'node' => $this,
+ );
+ }
+ break;
+ case '1' :
+ if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName]!==1) {
+ $repaired = false;
+ if ($options & self::REPAIR && isset($defaults[$propName])) {
+ $this->add($propName, $defaults[$propName]);
+ }
+ $messages[] = array(
+ 'level' => $repaired?1:3,
+ 'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
+ 'node' => $this,
+ );
+ }
+ break;
+ case '+' :
+ if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
+ $messages[] = array(
+ 'level' => 3,
+ 'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
+ 'node' => $this,
+ );
+ }
+ break;
+ case '*' :
+ break;
+ case '?' :
+ if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
+ $messages[] = array(
+ 'level' => 3,
+ 'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
+ 'node' => $this,
+ );
+ }
+ break;
+
+ }
+
+ }
+ return $messages;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Component/Available.php b/vendor/sabre/vobject/lib/Component/Available.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/Available.php
@@ -0,0 +1,108 @@
+ 1,
+ 'DTSTART' => 1,
+ 'DTSTAMP' => 1,
+
+ 'DTEND' => '?',
+ 'DURATION' => '?',
+
+ 'CREATED' => '?',
+ 'DESCRIPTION' => '?',
+ 'LAST-MODIFIED' => '?',
+ 'RECURRENCE-ID' => '?',
+ 'RRULE' => '?',
+ 'SUMMARY' => '?',
+
+ 'CATEGORIES' => '*',
+ 'COMMENT' => '*',
+ 'CONTACT' => '*',
+ 'EXDATE' => '*',
+ 'RDATE' => '*',
+
+ 'AVAILABLE' => '*',
+ );
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $result = parent::validate($options);
+
+ if (isset($this->DTEND) && isset($this->DURATION)) {
+ $result[] = array(
+ 'level' => 3,
+ 'message' => 'DTEND and DURATION cannot both be present',
+ 'node' => $this
+ );
+ }
+
+ if (isset($this->DURATION) && !isset($this->DTSTART)) {
+ $result[] = array(
+ 'level' => 3,
+ 'message' => 'DURATION must be declared with a DTSTART.',
+ 'node' => $this
+ );
+ }
+
+ return $result;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Component/VAlarm.php b/vendor/sabre/vobject/lib/Component/VAlarm.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VAlarm.php
@@ -0,0 +1,137 @@
+TRIGGER;
+ if(!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
+ $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
+ $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';
+
+ $parentComponent = $this->parent;
+ if ($related === 'START') {
+
+ if ($parentComponent->name === 'VTODO') {
+ $propName = 'DUE';
+ } else {
+ $propName = 'DTSTART';
+ }
+
+ $effectiveTrigger = clone $parentComponent->$propName->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ } else {
+ if ($parentComponent->name === 'VTODO') {
+ $endProp = 'DUE';
+ } elseif ($parentComponent->name === 'VEVENT') {
+ $endProp = 'DTEND';
+ } else {
+ throw new \LogicException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
+ }
+
+ if (isset($parentComponent->$endProp)) {
+ $effectiveTrigger = clone $parentComponent->$endProp->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ } elseif (isset($parentComponent->DURATION)) {
+ $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+ $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
+ $effectiveTrigger->add($duration);
+ $effectiveTrigger->add($triggerDuration);
+ } else {
+ $effectiveTrigger = clone $parentComponent->DTSTART->getDateTime();
+ $effectiveTrigger->add($triggerDuration);
+ }
+ }
+ } else {
+ $effectiveTrigger = $trigger->getDateTime();
+ }
+ return $effectiveTrigger;
+
+ }
+
+ /**
+ * Returns true or false depending on if the event falls in the specified
+ * time-range. This is used for filtering purposes.
+ *
+ * The rules used to determine if an event falls within the specified
+ * time-range is based on the CalDAV specification.
+ *
+ * @param \DateTime $start
+ * @param \DateTime $end
+ * @return bool
+ */
+ public function isInTimeRange(\DateTime $start, \DateTime $end) {
+
+ $effectiveTrigger = $this->getEffectiveTriggerTime();
+
+ if (isset($this->DURATION)) {
+ $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
+ $repeat = (string)$this->repeat;
+ if (!$repeat) {
+ $repeat = 1;
+ }
+
+ $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);
+
+ foreach($period as $occurrence) {
+
+ if ($start <= $occurrence && $end > $occurrence) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
+ }
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ public function getValidationRules() {
+
+ return array(
+ 'ACTION' => 1,
+ 'TRIGGER' => 1,
+
+ 'DURATION' => '?',
+ 'REPEAT' => '?',
+
+ 'ATTACH' => '?',
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Component/VAvailability.php b/vendor/sabre/vobject/lib/Component/VAvailability.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VAvailability.php
@@ -0,0 +1,99 @@
+ 1,
+ 'DTSTAMP' => 1,
+
+ 'BUSYTYPE' => '?',
+ 'CLASS' => '?',
+ 'CREATED' => '?',
+ 'DESCRIPTION' => '?',
+ 'DTSTART' => '?',
+ 'LAST-MODIFIED' => '?',
+ 'ORGANIZER' => '?',
+ 'PRIORITY' => '?',
+ 'SEQUENCE' => '?',
+ 'SUMMARY' => '?',
+ 'URL' => '?',
+ 'DTEND' => '?',
+ 'DURATION' => '?',
+
+ 'CATEGORIES' => '*',
+ 'COMMENT' => '*',
+ 'CONTACT' => '*',
+ );
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $result = parent::validate($options);
+
+ if (isset($this->DTEND) && isset($this->DURATION)) {
+ $result[] = array(
+ 'level' => 3,
+ 'message' => 'DTEND and DURATION cannot both be present',
+ 'node' => $this
+ );
+ }
+
+ return $result;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Component/VCalendar.php b/vendor/sabre/vobject/lib/Component/VCalendar.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VCalendar.php
@@ -0,0 +1,526 @@
+ 'Sabre\\VObject\\Component\\VAlarm',
+ 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
+ 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
+ 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
+ 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available',
+ 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
+ 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone',
+ 'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
+ );
+
+ /**
+ * List of value-types, and which classes they map to.
+ *
+ * @var array
+ */
+ static $valueMap = array(
+ 'BINARY' => 'Sabre\\VObject\\Property\\Binary',
+ 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
+ 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date',
+ 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+ 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
+ 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+ 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+ 'TEXT' => 'Sabre\\VObject\\Property\\Text',
+ 'TIME' => 'Sabre\\VObject\\Property\\Time',
+ 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
+ 'URI' => 'Sabre\\VObject\\Property\\Uri',
+ 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
+ );
+
+ /**
+ * List of properties, and which classes they map to.
+ *
+ * @var array
+ */
+ static $propertyMap = array(
+ // Calendar properties
+ 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'METHOD' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Component properties
+ 'ATTACH' => 'Sabre\\VObject\\Property\\Uri',
+ 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
+ 'CLASS' => 'Sabre\\VObject\\Property\\FlatText',
+ 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText',
+ 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'GEO' => 'Sabre\\VObject\\Property\\FloatValue',
+ 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'RESOURCES' => 'Sabre\\VObject\\Property\\Text',
+ 'STATUS' => 'Sabre\\VObject\\Property\\FlatText',
+ 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Date and Time Component Properties
+ 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+ 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+ 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Time Zone Component Properties
+ 'TZID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
+ 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset',
+ 'TZURL' => 'Sabre\\VObject\\Property\\Uri',
+
+ // Relationship Component Properties
+ 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText',
+ 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText',
+ 'URL' => 'Sabre\\VObject\\Property\\Uri',
+ 'UID' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Recurrence Component Properties
+ 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+ 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
+
+ // Alarm Component Properties
+ 'ACTION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+
+ // Change Management Component Properties
+ 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue',
+
+ // Request Status
+ 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
+
+ // Additions from draft-daboo-valarm-extensions-04
+ 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text',
+ 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text',
+ 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',
+
+ // Additions from draft-daboo-calendar-availability-05
+ 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text',
+
+ );
+
+ /**
+ * Returns the current document type.
+ *
+ * @return void
+ */
+ function getDocumentType() {
+
+ return self::ICALENDAR20;
+
+ }
+
+ /**
+ * Returns a list of all 'base components'. For instance, if an Event has
+ * a recurrence rule, and one instance is overridden, the overridden event
+ * will have the same UID, but will be excluded from this list.
+ *
+ * VTIMEZONE components will always be excluded.
+ *
+ * @param string $componentName filter by component name
+ * @return VObject\Component[]
+ */
+ function getBaseComponents($componentName = null) {
+
+ $components = array();
+ foreach($this->children as $component) {
+
+ if (!$component instanceof VObject\Component)
+ continue;
+
+ if (isset($component->{'RECURRENCE-ID'}))
+ continue;
+
+ if ($componentName && $component->name !== strtoupper($componentName))
+ continue;
+
+ if ($component->name === 'VTIMEZONE')
+ continue;
+
+ $components[] = $component;
+
+ }
+
+ return $components;
+
+ }
+
+ /**
+ * Returns the first component that is not a VTIMEZONE, and does not have
+ * an RECURRENCE-ID.
+ *
+ * If there is no such component, null will be returned.
+ *
+ * @param string $componentName filter by component name
+ * @return VObject\Component|null
+ */
+ function getBaseComponent($componentName = null) {
+
+ foreach($this->children as $component) {
+
+ if (!$component instanceof VObject\Component)
+ continue;
+
+ if (isset($component->{'RECURRENCE-ID'}))
+ continue;
+
+ if ($componentName && $component->name !== strtoupper($componentName))
+ continue;
+
+ if ($component->name === 'VTIMEZONE')
+ continue;
+
+ return $component;
+
+ }
+
+ }
+
+ /**
+ * If this calendar object, has events with recurrence rules, this method
+ * can be used to expand the event into multiple sub-events.
+ *
+ * Each event will be stripped from it's recurrence information, and only
+ * the instances of the event in the specified timerange will be left
+ * alone.
+ *
+ * In addition, this method will cause timezone information to be stripped,
+ * and normalized to UTC.
+ *
+ * This method will alter the VCalendar. This cannot be reversed.
+ *
+ * This functionality is specifically used by the CalDAV standard. It is
+ * possible for clients to request expand events, if they are rather simple
+ * clients and do not have the possibility to calculate recurrences.
+ *
+ * @param DateTime $start
+ * @param DateTime $end
+ * @param DateTimeZone $timeZone reference timezone for floating dates and
+ * times.
+ * @return void
+ */
+ function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) {
+
+ $newEvents = array();
+
+ if (!$timeZone) {
+ $timeZone = new DateTimeZone('UTC');
+ }
+
+ // An array of events. Events are indexed by UID. Each item in this
+ // array is a list of one or more events that match the UID.
+ $recurringEvents = array();
+
+ foreach($this->select('VEVENT') as $key=>$vevent) {
+
+ $uid = (string)$vevent->UID;
+ if (!$uid) {
+ throw new \LogicException('Event did not have a UID!');
+ }
+
+ if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) {
+ if (isset($recurringEvents[$uid])) {
+ $recurringEvents[$uid][] = $vevent;
+ } else {
+ $recurringEvents[$uid] = array($vevent);
+ }
+ continue;
+ }
+
+ if (!isset($vevent->RRULE)) {
+ if ($vevent->isInTimeRange($start, $end)) {
+ $newEvents[] = $vevent;
+ }
+ continue;
+ }
+
+ }
+
+ foreach($recurringEvents as $events) {
+
+ try {
+ $it = new EventIterator($events, $timeZone);
+
+ } catch (NoInstancesException $e) {
+ // This event is recurring, but it doesn't have a single
+ // instance. We are skipping this event from the output
+ // entirely.
+ continue;
+ }
+ $it->fastForward($start);
+
+ while($it->valid() && $it->getDTStart() < $end) {
+
+ if ($it->getDTEnd() > $start) {
+
+ $newEvents[] = $it->getEventObject();
+
+ }
+ $it->next();
+
+ }
+
+ }
+
+ // Wiping out all old VEVENT objects
+ unset($this->VEVENT);
+
+ // Setting all properties to UTC time.
+ foreach($newEvents as $newEvent) {
+
+ foreach($newEvent->children as $child) {
+ if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
+ $dt = $child->getDateTimes($timeZone);
+ // We only need to update the first timezone, because
+ // setDateTimes will match all other timezones to the
+ // first.
+ $dt[0]->setTimeZone(new DateTimeZone('UTC'));
+ $child->setDateTimes($dt);
+ }
+
+ }
+ $this->add($newEvent);
+
+ }
+
+ // Removing all VTIMEZONE components
+ unset($this->VTIMEZONE);
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return array(
+ 'VERSION' => '2.0',
+ 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
+ 'CALSCALE' => 'GREGORIAN',
+ );
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return array(
+ 'PRODID' => 1,
+ 'VERSION' => 1,
+
+ 'CALSCALE' => '?',
+ 'METHOD' => '?',
+ );
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $warnings = parent::validate($options);
+
+ if ($ver = $this->VERSION) {
+ if ((string)$ver !== '2.0') {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
+ 'node' => $this,
+ );
+ }
+
+ }
+
+ $uidList = array();
+
+ $componentsFound = 0;
+
+ $componentTypes = array();
+
+ foreach($this->children as $child) {
+ if($child instanceof Component) {
+ $componentsFound++;
+
+ if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) {
+ continue;
+ }
+ $componentTypes[] = $child->name;
+
+ $uid = (string)$child->UID;
+ $isMaster = isset($child->{'RECURRENCE-ID'})?0:1;
+ if (isset($uidList[$uid])) {
+ $uidList[$uid]['count']++;
+ if ($isMaster && $uidList[$uid]['hasMaster']) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'More than one master object was found for the object with UID ' . $uid,
+ 'node' => $this,
+ );
+ }
+ $uidList[$uid]['hasMaster']+=$isMaster;
+ } else {
+ $uidList[$uid] = array(
+ 'count' => 1,
+ 'hasMaster' => $isMaster,
+ );
+ }
+
+ }
+ }
+
+ if ($componentsFound===0) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'An iCalendar object must have at least 1 component.',
+ 'node' => $this,
+ );
+ }
+
+ if ($options & self::PROFILE_CALDAV) {
+ if (count($uidList)>1) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
+ 'node' => $this,
+ );
+ }
+ if (count(array_unique($componentTypes))===0) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
+ 'node' => $this,
+ );
+ }
+ if (count(array_unique($componentTypes))>1) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
+ 'node' => $this,
+ );
+ }
+
+ if (isset($this->METHOD)) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
+ 'node' => $this,
+ );
+ }
+ }
+
+ return $warnings;
+
+ }
+
+ /**
+ * Returns all components with a specific UID value.
+ *
+ * @return array
+ */
+ function getByUID($uid) {
+
+ return array_filter($this->children, function($item) use ($uid) {
+
+ if (!$item instanceof Component) {
+ return false;
+ }
+ if (!$itemUid = $item->select('UID')) {
+ return false;
+ }
+ $itemUid = current($itemUid)->getValue();
+ return $uid === $itemUid;
+
+ });
+
+ }
+
+
+}
diff --git a/vendor/sabre/vobject/lib/Component/VCard.php b/vendor/sabre/vobject/lib/Component/VCard.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VCard.php
@@ -0,0 +1,452 @@
+ 'Sabre\\VObject\\Property\\Binary',
+ 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
+ 'CONTENT-ID' => 'Sabre\\VObject\\Property\\FlatText', // vCard 2.1 only
+ 'DATE' => 'Sabre\\VObject\\Property\\VCard\\Date',
+ 'DATE-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateTime',
+ 'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
+ 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
+ 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'LANGUAGE-TAG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
+ 'TIMESTAMP' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
+ 'TEXT' => 'Sabre\\VObject\\Property\\Text',
+ 'TIME' => 'Sabre\\VObject\\Property\\Time',
+ 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
+ 'URI' => 'Sabre\\VObject\\Property\\Uri',
+ 'URL' => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
+ 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
+ );
+
+ /**
+ * List of properties, and which classes they map to.
+ *
+ * @var array
+ */
+ static $propertyMap = array(
+
+ // vCard 2.1 properties and up
+ 'N' => 'Sabre\\VObject\\Property\\Text',
+ 'FN' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PHOTO' => 'Sabre\\VObject\\Property\\Binary', // Todo: we should add a class for Binary values.
+ 'BDAY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+ 'ADR' => 'Sabre\\VObject\\Property\\Text',
+ 'LABEL' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+ 'TEL' => 'Sabre\\VObject\\Property\\FlatText',
+ 'EMAIL' => 'Sabre\\VObject\\Property\\FlatText',
+ 'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+ 'GEO' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TITLE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'ROLE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'LOGO' => 'Sabre\\VObject\\Property\\Binary',
+ // 'AGENT' => 'Sabre\\VObject\\Property\\', // Todo: is an embedded vCard. Probably rare, so
+ // not supported at the moment
+ 'ORG' => 'Sabre\\VObject\\Property\\Text',
+ 'NOTE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'REV' => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
+ 'SOUND' => 'Sabre\\VObject\\Property\\FlatText',
+ 'URL' => 'Sabre\\VObject\\Property\\Uri',
+ 'UID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'KEY' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TZ' => 'Sabre\\VObject\\Property\\Text',
+
+ // vCard 3.0 properties
+ 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
+ 'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'NICKNAME' => 'Sabre\\VObject\\Property\\Text',
+ 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
+
+ // rfc2739 properties
+ 'FBURL' => 'Sabre\\VObject\\Property\\Uri',
+ 'CAPURI' => 'Sabre\\VObject\\Property\\Uri',
+ 'CALURI' => 'Sabre\\VObject\\Property\\Uri',
+
+ // rfc4770 properties
+ 'IMPP' => 'Sabre\\VObject\\Property\\Uri',
+
+ // vCard 4.0 properties
+ 'XML' => 'Sabre\\VObject\\Property\\FlatText',
+ 'ANNIVERSARY' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+ 'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
+ 'LANG' => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
+ 'GENDER' => 'Sabre\\VObject\\Property\\Text',
+ 'KIND' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // rfc6474 properties
+ 'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'DEATHDATE' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
+
+ // rfc6715 properties
+ 'EXPERTISE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'HOBBY' => 'Sabre\\VObject\\Property\\FlatText',
+ 'INTEREST' => 'Sabre\\VObject\\Property\\FlatText',
+ 'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText',
+
+ );
+
+ /**
+ * Returns the current document type.
+ *
+ * @return void
+ */
+ function getDocumentType() {
+
+ if (!$this->version) {
+ $version = (string)$this->VERSION;
+ switch($version) {
+ case '2.1' :
+ $this->version = self::VCARD21;
+ break;
+ case '3.0' :
+ $this->version = self::VCARD30;
+ break;
+ case '4.0' :
+ $this->version = self::VCARD40;
+ break;
+ default :
+ $this->version = self::UNKNOWN;
+ break;
+
+ }
+ }
+
+ return $this->version;
+
+ }
+
+ /**
+ * Converts the document to a different vcard version.
+ *
+ * Use one of the VCARD constants for the target. This method will return
+ * a copy of the vcard in the new version.
+ *
+ * At the moment the only supported conversion is from 3.0 to 4.0.
+ *
+ * If input and output version are identical, a clone is returned.
+ *
+ * @param int $target
+ * @return VCard
+ */
+ function convert($target) {
+
+ $converter = new VObject\VCardConverter();
+ return $converter->convert($this, $target);
+
+ }
+
+ /**
+ * VCards with version 2.1, 3.0 and 4.0 are found.
+ *
+ * If the VCARD doesn't know its version, 2.1 is assumed.
+ */
+ const DEFAULT_VERSION = self::VCARD21;
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on)
+ * 2 - An inconsequential issue
+ * 3 - A severe issue.
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $warnings = array();
+
+ $versionMap = array(
+ self::VCARD21 => '2.1',
+ self::VCARD30 => '3.0',
+ self::VCARD40 => '4.0',
+ );
+
+ $version = $this->select('VERSION');
+ if (count($version)===1) {
+ $version = (string)$this->VERSION;
+ if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
+ 'node' => $this,
+ );
+ if ($options & self::REPAIR) {
+ $this->VERSION = $versionMap[self::DEFAULT_VERSION];
+ }
+ }
+ if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) {
+ $warnings[] = array(
+ 'level' => 3,
+ 'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
+ 'node' => $this,
+ );
+ }
+
+ }
+ $uid = $this->select('UID');
+ if (count($uid) === 0) {
+ if ($options & self::PROFILE_CARDDAV) {
+ // Required for CardDAV
+ $warningLevel = 3;
+ $message = 'vCards on CardDAV servers MUST have a UID property.';
+ } else {
+ // Not required for regular vcards
+ $warningLevel = 2;
+ $message = 'Adding a UID to a vCard property is recommended.';
+ }
+ if ($options & self::REPAIR) {
+ $this->UID = VObject\UUIDUtil::getUUID();
+ $warningLevel = 1;
+ }
+ $warnings[] = array(
+ 'level' => $warningLevel,
+ 'message' => $message,
+ 'node' => $this,
+ );
+ }
+
+ $fn = $this->select('FN');
+ if (count($fn)!==1) {
+
+ $repaired = false;
+ if (($options & self::REPAIR) && count($fn) === 0) {
+ // We're going to try to see if we can use the contents of the
+ // N property.
+ if (isset($this->N)) {
+ $value = explode(';', (string)$this->N);
+ if (isset($value[1]) && $value[1]) {
+ $this->FN = $value[1] . ' ' . $value[0];
+ } else {
+ $this->FN = $value[0];
+ }
+ $repaired = true;
+
+ // Otherwise, the ORG property may work
+ } elseif (isset($this->ORG)) {
+ $this->FN = (string)$this->ORG;
+ $repaired = true;
+ }
+
+ }
+ $warnings[] = array(
+ 'level' => $repaired?1:3,
+ 'message' => 'The FN property must appear in the VCARD component exactly 1 time',
+ 'node' => $this,
+ );
+ }
+
+ return array_merge(
+ parent::validate($options),
+ $warnings
+ );
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return array(
+ 'ADR' => '*',
+ 'ANNIVERSARY' => '?',
+ 'BDAY' => '?',
+ 'CALADRURI' => '*',
+ 'CALURI' => '*',
+ 'CATEGORIES' => '*',
+ 'CLIENTPIDMAP' => '*',
+ 'EMAIL' => '*',
+ 'FBURL' => '*',
+ 'IMPP' => '*',
+ 'GENDER' => '?',
+ 'GEO' => '*',
+ 'KEY' => '*',
+ 'KIND' => '?',
+ 'LANG' => '*',
+ 'LOGO' => '*',
+ 'MEMBER' => '*',
+ 'N' => '?',
+ 'NICKNAME' => '*',
+ 'NOTE' => '*',
+ 'ORG' => '*',
+ 'PHOTO' => '*',
+ 'PRODID' => '?',
+ 'RELATED' => '*',
+ 'REV' => '?',
+ 'ROLE' => '*',
+ 'SOUND' => '*',
+ 'SOURCE' => '*',
+ 'TEL' => '*',
+ 'TITLE' => '*',
+ 'TZ' => '*',
+ 'URL' => '*',
+ 'VERSION' => '1',
+ 'XML' => '*',
+
+ // FN is commented out, because it's already handled by the
+ // validate function, which may also try to repair it.
+ // 'FN' => '+',
+
+ 'UID' => '?',
+ );
+
+ }
+
+ /**
+ * Returns a preferred field.
+ *
+ * VCards can indicate wether a field such as ADR, TEL or EMAIL is
+ * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
+ * being a number between 1 and 100).
+ *
+ * If neither of those parameters are specified, the first is returned, if
+ * a field with that name does not exist, null is returned.
+ *
+ * @param string $fieldName
+ * @return VObject\Property|null
+ */
+ function preferred($propertyName) {
+
+ $preferred = null;
+ $lastPref = 101;
+ foreach($this->select($propertyName) as $field) {
+
+ $pref = 101;
+ if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
+ $pref = 1;
+ } elseif (isset($field['PREF'])) {
+ $pref = $field['PREF']->getValue();
+ }
+
+ if ($pref < $lastPref || is_null($preferred)) {
+ $preferred = $field;
+ $lastPref = $pref;
+ }
+
+ }
+ return $preferred;
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return array(
+ 'VERSION' => '3.0',
+ 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
+ );
+
+ }
+
+ /**
+ * This method returns an array, with the representation as it should be
+ * encoded in json. This is used to create jCard or jCal documents.
+ *
+ * @return array
+ */
+ function jsonSerialize() {
+
+ // A vcard does not have sub-components, so we're overriding this
+ // method to remove that array element.
+ $properties = array();
+
+ foreach($this->children as $child) {
+ $properties[] = $child->jsonSerialize();
+ }
+
+ return array(
+ strtolower($this->name),
+ $properties,
+ );
+
+ }
+
+ /**
+ * Returns the default class for a property name.
+ *
+ * @param string $propertyName
+ * @return string
+ */
+ function getClassNameForPropertyName($propertyName) {
+
+ $className = parent::getClassNameForPropertyName($propertyName);
+ // In vCard 4, BINARY no longer exists, and we need URI instead.
+
+ if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType()===self::VCARD40) {
+ return 'Sabre\\VObject\\Property\\Uri';
+ }
+ return $className;
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/lib/Component/VEvent.php b/vendor/sabre/vobject/lib/Component/VEvent.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VEvent.php
@@ -0,0 +1,153 @@
+RRULE) {
+
+ try {
+
+ $it = new EventIterator($this, null, $start->getTimezone());
+
+ } catch (NoInstancesException $e) {
+
+ // If we've catched this exception, there are no instances
+ // for the event that fall into the specified time-range.
+ return false;
+
+ }
+
+ $it->fastForward($start);
+
+ // We fast-forwarded to a spot where the end-time of the
+ // recurrence instance exceeded the start of the requested
+ // time-range.
+ //
+ // If the starttime of the recurrence did not exceed the
+ // end of the time range as well, we have a match.
+ return ($it->getDTStart() < $end && $it->getDTEnd() > $start);
+
+ }
+
+ $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone());
+ if (isset($this->DTEND)) {
+
+ // The DTEND property is considered non inclusive. So for a 3 day
+ // event in july, dtstart and dtend would have to be July 1st and
+ // July 4th respectively.
+ //
+ // See:
+ // http://tools.ietf.org/html/rfc5545#page-54
+ $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone());
+
+ } elseif (isset($this->DURATION)) {
+ $effectiveEnd = clone $effectiveStart;
+ $effectiveEnd->add(VObject\DateTimeParser::parseDuration($this->DURATION));
+ } elseif (!$this->DTSTART->hasTime()) {
+ $effectiveEnd = clone $effectiveStart;
+ $effectiveEnd->modify('+1 day');
+ } else {
+ $effectiveEnd = clone $effectiveStart;
+ }
+ return (
+ ($start < $effectiveEnd) && ($end > $effectiveStart)
+ );
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return array(
+ 'UID' => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
+ 'DTSTAMP' => date('Ymd\\THis\\Z'),
+ );
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ public function getValidationRules() {
+
+ $hasMethod = isset($this->parent->METHOD);
+ return array(
+ 'UID' => 1,
+ 'DTSTAMP' => 1,
+ 'DTSTART' => $hasMethod?'?':'1',
+ 'CLASS' => '?',
+ 'CREATED' => '?',
+ 'DESCRIPTION' => '?',
+ 'GEO' => '?',
+ 'LAST-MODIFIED' => '?',
+ 'LOCATION' => '?',
+ 'ORGANIZER' => '?',
+ 'PRIORITY' => '?',
+ 'SEQUENCE' => '?',
+ 'STATUS' => '?',
+ 'SUMMARY' => '?',
+ 'TRANSP' => '?',
+ 'URL' => '?',
+ 'RECURRENCE-ID' => '?',
+ 'RRULE' => '?',
+ 'DTEND' => '?',
+ 'DURATION' => '?',
+
+ 'ATTACH' => '*',
+ 'ATTENDEE' => '*',
+ 'CATEGORIES' => '*',
+ 'COMMENT' => '*',
+ 'CONTACT' => '*',
+ 'EXDATE' => '*',
+ 'REQUEST-STATUS' => '*',
+ 'RELATED-TO' => '*',
+ 'RESOURCES' => '*',
+ 'RDATE' => '*',
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Component/VFreeBusy.php b/vendor/sabre/vobject/lib/Component/VFreeBusy.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VFreeBusy.php
@@ -0,0 +1,103 @@
+select('FREEBUSY') as $freebusy) {
+
+ // We are only interested in FBTYPE=BUSY (the default),
+ // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
+ if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'],0,4))!=='BUSY') {
+ continue;
+ }
+
+ // The freebusy component can hold more than 1 value, separated by
+ // commas.
+ $periods = explode(',', (string)$freebusy);
+
+ foreach($periods as $period) {
+ // Every period is formatted as [start]/[end]. The start is an
+ // absolute UTC time, the end may be an absolute UTC time, or
+ // duration (relative) value.
+ list($busyStart, $busyEnd) = explode('/', $period);
+
+ $busyStart = VObject\DateTimeParser::parse($busyStart);
+ $busyEnd = VObject\DateTimeParser::parse($busyEnd);
+ if ($busyEnd instanceof \DateInterval) {
+ $tmp = clone $busyStart;
+ $tmp->add($busyEnd);
+ $busyEnd = $tmp;
+ }
+
+ if($start < $busyEnd && $end > $busyStart) {
+ return false;
+ }
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ public function getValidationRules() {
+
+ return array(
+ 'UID' => 1,
+ 'DTSTAMP' => 1,
+
+ 'CONTACT' => '?',
+ 'DTSTART' => '?',
+ 'DTEND' => '?',
+ 'ORGANIZER' => '?',
+ 'URL' => '?',
+
+ 'ATTENDEE' => '*',
+ 'COMMENT' => '*',
+ 'FREEBUSY' => '*',
+ 'REQUEST-STATUS' => '*',
+ );
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/lib/Component/VJournal.php b/vendor/sabre/vobject/lib/Component/VJournal.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VJournal.php
@@ -0,0 +1,91 @@
+DTSTART)?$this->DTSTART->getDateTime():null;
+ if ($dtstart) {
+ $effectiveEnd = clone $dtstart;
+ if (!$this->DTSTART->hasTime()) {
+ $effectiveEnd->modify('+1 day');
+ }
+
+ return ($start <= $effectiveEnd && $end > $dtstart);
+
+ }
+ return false;
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ public function getValidationRules() {
+
+ return array(
+ 'UID' => 1,
+ 'DTSTAMP' => 1,
+
+ 'CLASS' => '?',
+ 'CREATED' => '?',
+ 'DTSTART' => '?',
+ 'LAST-MODIFIED' => '?',
+ 'ORGANIZER' => '?',
+ 'RECURRENCE-ID' => '?',
+ 'SEQUENCE' => '?',
+ 'STATUS' => '?',
+ 'SUMMARY' => '?',
+ 'URL' => '?',
+
+ 'RRULE' => '?',
+
+ 'ATTACH' => '*',
+ 'ATTENDEE' => '*',
+ 'CATEGORIES' => '*',
+ 'COMMENT' => '*',
+ 'CONTACT' => '*',
+ 'DESCRIPTION' => '*',
+ 'EXDATE' => '*',
+ 'RELATED-TO' => '*',
+ 'RDATE' => '*',
+ );
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Component/VTimeZone.php b/vendor/sabre/vobject/lib/Component/VTimeZone.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VTimeZone.php
@@ -0,0 +1,68 @@
+TZID, $this->root);
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return array(
+ 'TZID' => 1,
+
+ 'LAST-MODIFIED' => '?',
+ 'TZURL' => '?',
+
+ // At least 1 STANDARD or DAYLIGHT must appear, or more. But both
+ // cannot appear in the same VTIMEZONE.
+ //
+ // The validator is not specific yet to pick this up, so these
+ // rules are too loose.
+ 'STANDARD' => '*',
+ 'DAYLIGHT' => '*',
+ );
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/lib/Component/VTodo.php b/vendor/sabre/vobject/lib/Component/VTodo.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VTodo.php
@@ -0,0 +1,177 @@
+DTSTART)?$this->DTSTART->getDateTime():null;
+ $duration = isset($this->DURATION)?VObject\DateTimeParser::parseDuration($this->DURATION):null;
+ $due = isset($this->DUE)?$this->DUE->getDateTime():null;
+ $completed = isset($this->COMPLETED)?$this->COMPLETED->getDateTime():null;
+ $created = isset($this->CREATED)?$this->CREATED->getDateTime():null;
+
+ if ($dtstart) {
+ if ($duration) {
+ $effectiveEnd = clone $dtstart;
+ $effectiveEnd->add($duration);
+ return $start <= $effectiveEnd && $end > $dtstart;
+ } elseif ($due) {
+ return
+ ($start < $due || $start <= $dtstart) &&
+ ($end > $dtstart || $end >= $due);
+ } else {
+ return $start <= $dtstart && $end > $dtstart;
+ }
+ }
+ if ($due) {
+ return ($start < $due && $end >= $due);
+ }
+ if ($completed && $created) {
+ return
+ ($start <= $created || $start <= $completed) &&
+ ($end >= $created || $end >= $completed);
+ }
+ if ($completed) {
+ return ($start <= $completed && $end >= $completed);
+ }
+ if ($created) {
+ return ($end > $created);
+ }
+ return true;
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ public function getValidationRules() {
+
+ return array(
+ 'UID' => 1,
+ 'DTSTAMP' => 1,
+
+ 'CLASS' => '?',
+ 'COMPLETED' => '?',
+ 'CREATED' => '?',
+ 'DESCRIPTION' => '?',
+ 'DTSTART' => '?',
+ 'GEO' => '?',
+ 'LAST-MODIFIED' => '?',
+ 'LOCATION' => '?',
+ 'ORGANIZER' => '?',
+ 'PERCENT' => '?',
+ 'PRIORITY' => '?',
+ 'RECURRENCE-ID' => '?',
+ 'SEQUENCE' => '?',
+ 'STATUS' => '?',
+ 'SUMMARY' => '?',
+ 'URL' => '?',
+
+ 'RRULE' => '?',
+ 'DUE' => '?',
+ 'DURATION' => '?',
+
+ 'ATTACH' => '*',
+ 'ATTENDEE' => '*',
+ 'CATEGORIES' => '*',
+ 'COMMENT' => '*',
+ 'CONTACT' => '*',
+ 'EXDATE' => '*',
+ 'REQUEST-STATUS' => '*',
+ 'RELATED-TO' => '*',
+ 'RESOURCES' => '*',
+ 'RDATE' => '*',
+ );
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on)
+ * 2 - An inconsequential issue
+ * 3 - A severe issue.
+ *
+ * @param int $options
+ * @return array
+ */
+ public function validate($options = 0) {
+
+ $result = parent::validate($options);
+ if (isset($this->DUE) && isset($this->DTSTART)) {
+
+ $due = $this->DUE;
+ $dtStart = $this->DTSTART;
+
+ if ($due->getValueType() !== $dtStart->getValueType()) {
+
+ $result[] = array(
+ 'level' => 3,
+ 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART',
+ 'node' => $due,
+ );
+
+ } elseif ($due->getDateTime() < $dtStart->getDateTime()) {
+
+ $result[] = array(
+ 'level' => 3,
+ 'message' => 'DUE must occur after DTSTART',
+ 'node' => $due,
+ );
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/DateTimeParser.php b/vendor/sabre/vobject/lib/DateTimeParser.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/DateTimeParser.php
@@ -0,0 +1,431 @@
+setTimeZone(new \DateTimeZone('UTC'));
+ return $date;
+
+ }
+
+ /**
+ * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object.
+ *
+ * @param string $date
+ * @param DateTimeZone $tz
+ * @return DateTime
+ */
+ static public function parseDate($date, DateTimeZone $tz = null) {
+
+ // Format is YYYYMMDD
+ $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
+
+ if (!$result) {
+ throw new LogicException('The supplied iCalendar date value is incorrect: ' . $date);
+ }
+
+ if (is_null($tz)) {
+ $tz = new DateTimeZone('UTC');
+ }
+
+ $date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
+ return $date;
+
+ }
+
+ /**
+ * Parses an iCalendar (RFC5545) formatted duration value.
+ *
+ * This method will either return a DateTimeInterval object, or a string
+ * suitable for strtotime or DateTime::modify.
+ *
+ * @param string $duration
+ * @param bool $asString
+ * @return DateInterval|string
+ */
+ static public function parseDuration($duration, $asString = false) {
+
+ $result = preg_match('/^(?P\+|-)?P((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$/', $duration, $matches);
+ if (!$result) {
+ throw new LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
+ }
+
+ if (!$asString) {
+ $invert = false;
+ if ($matches['plusminus']==='-') {
+ $invert = true;
+ }
+
+
+ $parts = array(
+ 'week',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ );
+ foreach($parts as $part) {
+ $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
+ }
+
+
+ // We need to re-construct the $duration string, because weeks and
+ // days are not supported by DateInterval in the same string.
+ $duration = 'P';
+ $days = $matches['day'];
+ if ($matches['week']) {
+ $days+=$matches['week']*7;
+ }
+ if ($days)
+ $duration.=$days . 'D';
+
+ if ($matches['minute'] || $matches['second'] || $matches['hour']) {
+ $duration.='T';
+
+ if ($matches['hour'])
+ $duration.=$matches['hour'].'H';
+
+ if ($matches['minute'])
+ $duration.=$matches['minute'].'M';
+
+ if ($matches['second'])
+ $duration.=$matches['second'].'S';
+
+ }
+
+ if ($duration==='P') {
+ $duration = 'PT0S';
+ }
+ $iv = new DateInterval($duration);
+ if ($invert) $iv->invert = true;
+
+ return $iv;
+
+ }
+
+
+
+ $parts = array(
+ 'week',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ );
+
+ $newDur = '';
+ foreach($parts as $part) {
+ if (isset($matches[$part]) && $matches[$part]) {
+ $newDur.=' '.$matches[$part] . ' ' . $part . 's';
+ }
+ }
+
+ $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
+ if ($newDur === '+') {
+ $newDur = '+0 seconds';
+ };
+ return $newDur;
+
+ }
+
+ /**
+ * Parses either a Date or DateTime, or Duration value.
+ *
+ * @param string $date
+ * @param DateTimeZone|string $referenceTz
+ * @return DateTime|DateInterval
+ */
+ static public function parse($date, $referenceTz = null) {
+
+ if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
+ return self::parseDuration($date);
+ } elseif (strlen($date)===8) {
+ return self::parseDate($date, $referenceTz);
+ } else {
+ return self::parseDateTime($date, $referenceTz);
+ }
+
+ }
+
+ /**
+ * This method parses a vCard date and or time value.
+ *
+ * This can be used for the DATE, DATE-TIME, TIMESTAMP and
+ * DATE-AND-OR-TIME value.
+ *
+ * This method returns an array, not a DateTime value.
+ *
+ * The elements in the array are in the following order:
+ * year, month, date, hour, minute, second, timezone
+ *
+ * Almost any part of the string may be omitted. It's for example legal to
+ * just specify seconds, leave out the year, etc.
+ *
+ * Timezone is either returned as 'Z' or as '+08:00'
+ *
+ * For any non-specified values null is returned.
+ *
+ * List of date formats that are supported:
+ * YYYY
+ * YYYY-MM
+ * YYYYMMDD
+ * --MMDD
+ * ---DD
+ *
+ * YYYY-MM-DD
+ * --MM-DD
+ * ---DD
+ *
+ * List of supported time formats:
+ *
+ * HH
+ * HHMM
+ * HHMMSS
+ * -MMSS
+ * --SS
+ *
+ * HH
+ * HH:MM
+ * HH:MM:SS
+ * -MM:SS
+ * --SS
+ *
+ * A full basic-format date-time string looks like :
+ * 20130603T133901
+ *
+ * A full extended-format date-time string looks like :
+ * 2013-06-03T13:39:01
+ *
+ * Times may be postfixed by a timezone offset. This can be either 'Z' for
+ * UTC, or a string like -0500 or +1100.
+ *
+ * @param string $date
+ * @return array
+ */
+ static public function parseVCardDateTime($date) {
+
+ $regex = '/^
+ (?: # date part
+ (?:
+ (?: (?P [0-9]{4}) (?: -)?| --)
+ (?P [0-9]{2})?
+ |---)
+ (?P [0-9]{2})?
+ )?
+ (?:T # time part
+ (?P [0-9]{2} | -)
+ (?P [0-9]{2} | -)?
+ (?P [0-9]{2})?
+
+ (?: \.[0-9]{3})? # milliseconds
+ (?P # timezone offset
+
+ Z | (?: \+|-)(?: [0-9]{4})
+
+ )?
+
+ )?
+ $/x';
+
+ if (!preg_match($regex, $date, $matches)) {
+
+ // Attempting to parse the extended format.
+ $regex = '/^
+ (?: # date part
+ (?: (?P [0-9]{4}) - | -- )
+ (?P [0-9]{2}) -
+ (?P [0-9]{2})
+ )?
+ (?:T # time part
+
+ (?: (?P [0-9]{2}) : | -)
+ (?: (?P [0-9]{2}) : | -)?
+ (?P [0-9]{2})?
+
+ (?: \.[0-9]{3})? # milliseconds
+ (?P # timezone offset
+
+ Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
+
+ )?
+
+ )?
+ $/x';
+
+ if (!preg_match($regex, $date, $matches)) {
+ throw new InvalidArgumentException('Invalid vCard date-time string: ' . $date);
+ }
+
+ }
+ $parts = array(
+ 'year',
+ 'month',
+ 'date',
+ 'hour',
+ 'minute',
+ 'second',
+ 'timezone'
+ );
+
+ $result = array();
+ foreach($parts as $part) {
+
+ if (empty($matches[$part])) {
+ $result[$part] = null;
+ } elseif ($matches[$part] === '-' || $matches[$part] === '--') {
+ $result[$part] = null;
+ } else {
+ $result[$part] = $matches[$part];
+ }
+
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * This method parses a vCard TIME value.
+ *
+ * This method returns an array, not a DateTime value.
+ *
+ * The elements in the array are in the following order:
+ * hour, minute, second, timezone
+ *
+ * Almost any part of the string may be omitted. It's for example legal to
+ * just specify seconds, leave out the hour etc.
+ *
+ * Timezone is either returned as 'Z' or as '+08:00'
+ *
+ * For any non-specified values null is returned.
+ *
+ * List of supported time formats:
+ *
+ * HH
+ * HHMM
+ * HHMMSS
+ * -MMSS
+ * --SS
+ *
+ * HH
+ * HH:MM
+ * HH:MM:SS
+ * -MM:SS
+ * --SS
+ *
+ * A full basic-format time string looks like :
+ * 133901
+ *
+ * A full extended-format time string looks like :
+ * 13:39:01
+ *
+ * Times may be postfixed by a timezone offset. This can be either 'Z' for
+ * UTC, or a string like -0500 or +11:00.
+ *
+ * @param string $date
+ * @return array
+ */
+ static public function parseVCardTime($date) {
+
+ $regex = '/^
+ (?P [0-9]{2} | -)
+ (?P [0-9]{2} | -)?
+ (?P [0-9]{2})?
+
+ (?: \.[0-9]{3})? # milliseconds
+ (?P # timezone offset
+
+ Z | (?: \+|-)(?: [0-9]{4})
+
+ )?
+ $/x';
+
+
+ if (!preg_match($regex, $date, $matches)) {
+
+ // Attempting to parse the extended format.
+ $regex = '/^
+ (?: (?P [0-9]{2}) : | -)
+ (?: (?P [0-9]{2}) : | -)?
+ (?P [0-9]{2})?
+
+ (?: \.[0-9]{3})? # milliseconds
+ (?P # timezone offset
+
+ Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})
+
+ )?
+ $/x';
+
+ if (!preg_match($regex, $date, $matches)) {
+ throw new InvalidArgumentException('Invalid vCard time string: ' . $date);
+ }
+
+ }
+ $parts = array(
+ 'hour',
+ 'minute',
+ 'second',
+ 'timezone'
+ );
+
+ $result = array();
+ foreach($parts as $part) {
+
+ if (empty($matches[$part])) {
+ $result[$part] = null;
+ } elseif ($matches[$part] === '-') {
+ $result[$part] = null;
+ } else {
+ $result[$part] = $matches[$part];
+ }
+
+ }
+
+ return $result;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Document.php b/vendor/sabre/vobject/lib/Document.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Document.php
@@ -0,0 +1,261 @@
+value syntax, in which case
+ * properties will automatically be created, or you can just pass a list of
+ * Component and Property object.
+ *
+ * By default, a set of sensible values will be added to the component. For
+ * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
+ * ensure that this does not happen, set $defaults to false.
+ *
+ * @param string $name
+ * @param array $children
+ * @param bool $defaults
+ * @return Component
+ */
+ public function createComponent($name, array $children = null, $defaults = true) {
+
+ $name = strtoupper($name);
+ $class = 'Sabre\\VObject\\Component';
+
+ if (isset(static::$componentMap[$name])) {
+ $class=static::$componentMap[$name];
+ }
+ if (is_null($children)) $children = array();
+ return new $class($this, $name, $children, $defaults);
+
+ }
+
+ /**
+ * Factory method for creating new properties
+ *
+ * This method automatically searches for the correct property class, based
+ * on its name.
+ *
+ * You can specify the parameters either in key=>value syntax, in which case
+ * parameters will automatically be created, or you can just pass a list of
+ * Parameter objects.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @param array $parameters
+ * @param string $valueType Force a specific valuetype, such as URI or TEXT
+ * @return Property
+ */
+ public function createProperty($name, $value = null, array $parameters = null, $valueType = null) {
+
+ // If there's a . in the name, it means it's prefixed by a groupname.
+ if (($i=strpos($name,'.'))!==false) {
+ $group = substr($name, 0, $i);
+ $name = strtoupper(substr($name, $i+1));
+ } else {
+ $name = strtoupper($name);
+ $group = null;
+ }
+
+ $class = null;
+
+ if ($valueType) {
+ // The valueType argument comes first to figure out the correct
+ // class.
+ $class = $this->getClassNameForPropertyValue($valueType);
+ }
+
+ if (is_null($class) && isset($parameters['VALUE'])) {
+ // If a VALUE parameter is supplied, we should use that.
+ $class = $this->getClassNameForPropertyValue($parameters['VALUE']);
+ }
+ if (is_null($class)) {
+ $class = $this->getClassNameForPropertyName($name);
+ }
+ if (is_null($parameters)) $parameters = array();
+
+ return new $class($this, $name, $value, $parameters, $group);
+
+ }
+
+ /**
+ * This method returns a full class-name for a value parameter.
+ *
+ * For instance, DTSTART may have VALUE=DATE. In that case we will look in
+ * our valueMap table and return the appropriate class name.
+ *
+ * This method returns null if we don't have a specialized class.
+ *
+ * @param string $valueParam
+ * @return void
+ */
+ public function getClassNameForPropertyValue($valueParam) {
+
+ $valueParam = strtoupper($valueParam);
+ if (isset(static::$valueMap[$valueParam])) {
+ return static::$valueMap[$valueParam];
+ }
+
+ }
+
+ /**
+ * Returns the default class for a property name.
+ *
+ * @param string $propertyName
+ * @return string
+ */
+ public function getClassNameForPropertyName($propertyName) {
+
+ if (isset(static::$propertyMap[$propertyName])) {
+ return static::$propertyMap[$propertyName];
+ } else {
+ return 'Sabre\\VObject\\Property\\Unknown';
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/ElementList.php b/vendor/sabre/vobject/lib/ElementList.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/ElementList.php
@@ -0,0 +1,172 @@
+vevent where there's multiple VEVENT objects.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ElementList implements \Iterator, \Countable, \ArrayAccess {
+
+ /**
+ * Inner elements
+ *
+ * @var array
+ */
+ protected $elements = array();
+
+ /**
+ * Creates the element list.
+ *
+ * @param array $elements
+ */
+ public function __construct(array $elements) {
+
+ $this->elements = $elements;
+
+ }
+
+ /* {{{ Iterator interface */
+
+ /**
+ * Current position
+ *
+ * @var int
+ */
+ private $key = 0;
+
+ /**
+ * Returns current item in iteration
+ *
+ * @return Element
+ */
+ public function current() {
+
+ return $this->elements[$this->key];
+
+ }
+
+ /**
+ * To the next item in the iterator
+ *
+ * @return void
+ */
+ public function next() {
+
+ $this->key++;
+
+ }
+
+ /**
+ * Returns the current iterator key
+ *
+ * @return int
+ */
+ public function key() {
+
+ return $this->key;
+
+ }
+
+ /**
+ * Returns true if the current position in the iterator is a valid one
+ *
+ * @return bool
+ */
+ public function valid() {
+
+ return isset($this->elements[$this->key]);
+
+ }
+
+ /**
+ * Rewinds the iterator
+ *
+ * @return void
+ */
+ public function rewind() {
+
+ $this->key = 0;
+
+ }
+
+ /* }}} */
+
+ /* {{{ Countable interface */
+
+ /**
+ * Returns the number of elements
+ *
+ * @return int
+ */
+ public function count() {
+
+ return count($this->elements);
+
+ }
+
+ /* }}} */
+
+ /* {{{ ArrayAccess Interface */
+
+
+ /**
+ * Checks if an item exists through ArrayAccess.
+ *
+ * @param int $offset
+ * @return bool
+ */
+ public function offsetExists($offset) {
+
+ return isset($this->elements[$offset]);
+
+ }
+
+ /**
+ * Gets an item through ArrayAccess.
+ *
+ * @param int $offset
+ * @return mixed
+ */
+ public function offsetGet($offset) {
+
+ return $this->elements[$offset];
+
+ }
+
+ /**
+ * Sets an item through ArrayAccess.
+ *
+ * @param int $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value) {
+
+ throw new \LogicException('You can not add new objects to an ElementList');
+
+ }
+
+ /**
+ * Sets an item through ArrayAccess.
+ *
+ * This method just forwards the request to the inner iterator
+ *
+ * @param int $offset
+ * @return void
+ */
+ public function offsetUnset($offset) {
+
+ throw new \LogicException('You can not remove objects from an ElementList');
+
+ }
+
+ /* }}} */
+
+}
diff --git a/vendor/sabre/vobject/lib/EofException.php b/vendor/sabre/vobject/lib/EofException.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/EofException.php
@@ -0,0 +1,15 @@
+setTimeRange($start, $end);
+ }
+
+ if ($objects) {
+ $this->setObjects($objects);
+ }
+ if (is_null($timeZone)) {
+ $timeZone = new DateTimeZone('UTC');
+ }
+ $this->setTimeZone($timeZone);
+
+ }
+
+ /**
+ * Sets the VCALENDAR object.
+ *
+ * If this is set, it will not be generated for you. You are responsible
+ * for setting things like the METHOD, CALSCALE, VERSION, etc..
+ *
+ * The VFREEBUSY object will be automatically added though.
+ *
+ * @param Component $vcalendar
+ * @return void
+ */
+ public function setBaseObject(Component $vcalendar) {
+
+ $this->baseObject = $vcalendar;
+
+ }
+
+ /**
+ * Sets the input objects
+ *
+ * You must either specify a valendar object as a strong, or as the parse
+ * Component.
+ * It's also possible to specify multiple objects as an array.
+ *
+ * @param mixed $objects
+ * @return void
+ */
+ public function setObjects($objects) {
+
+ if (!is_array($objects)) {
+ $objects = array($objects);
+ }
+
+ $this->objects = array();
+ foreach($objects as $object) {
+
+ if (is_string($object)) {
+ $this->objects[] = Reader::read($object);
+ } elseif ($object instanceof Component) {
+ $this->objects[] = $object;
+ } else {
+ throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
+ }
+
+ }
+
+ }
+
+ /**
+ * Sets the time range
+ *
+ * Any freebusy object falling outside of this time range will be ignored.
+ *
+ * @param DateTime $start
+ * @param DateTime $end
+ * @return void
+ */
+ public function setTimeRange(\DateTime $start = null, \DateTime $end = null) {
+
+ $this->start = $start;
+ $this->end = $end;
+
+ }
+
+ /**
+ * Sets the reference timezone for floating times.
+ *
+ * @param DateTimeZone $timeZone
+ * @return void
+ */
+ public function setTimeZone(DateTimeZone $timeZone) {
+
+ $this->timeZone = $timeZone;
+
+ }
+
+ /**
+ * Parses the input data and returns a correct VFREEBUSY object, wrapped in
+ * a VCALENDAR.
+ *
+ * @return Component
+ */
+ public function getResult() {
+
+ $busyTimes = array();
+
+ foreach($this->objects as $key=>$object) {
+
+ foreach($object->getBaseComponents() as $component) {
+
+ switch($component->name) {
+
+ case 'VEVENT' :
+
+ $FBTYPE = 'BUSY';
+ if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
+ break;
+ }
+ if (isset($component->STATUS)) {
+ $status = strtoupper($component->STATUS);
+ if ($status==='CANCELLED') {
+ break;
+ }
+ if ($status==='TENTATIVE') {
+ $FBTYPE = 'BUSY-TENTATIVE';
+ }
+ }
+
+ $times = array();
+
+ if ($component->RRULE) {
+ try {
+ $iterator = new EventIterator($object, (string)$component->uid, $this->timeZone);
+ } catch (NoInstancesException $e) {
+ // This event is recurring, but it doesn't have a single
+ // instance. We are skipping this event from the output
+ // entirely.
+ unset($this->objects[$key]);
+ continue;
+ }
+
+ if ($this->start) {
+ $iterator->fastForward($this->start);
+ }
+
+ $maxRecurrences = 200;
+
+ while($iterator->valid() && --$maxRecurrences) {
+
+ $startTime = $iterator->getDTStart();
+ if ($this->end && $startTime > $this->end) {
+ break;
+ }
+ $times[] = array(
+ $iterator->getDTStart(),
+ $iterator->getDTEnd(),
+ );
+
+ $iterator->next();
+
+ }
+
+ } else {
+
+ $startTime = $component->DTSTART->getDateTime($this->timeZone);
+ if ($this->end && $startTime > $this->end) {
+ break;
+ }
+ $endTime = null;
+ if (isset($component->DTEND)) {
+ $endTime = $component->DTEND->getDateTime($this->timeZone);
+ } elseif (isset($component->DURATION)) {
+ $duration = DateTimeParser::parseDuration((string)$component->DURATION);
+ $endTime = clone $startTime;
+ $endTime->add($duration);
+ } elseif (!$component->DTSTART->hasTime()) {
+ $endTime = clone $startTime;
+ $endTime->modify('+1 day');
+ } else {
+ // The event had no duration (0 seconds)
+ break;
+ }
+
+ $times[] = array($startTime, $endTime);
+
+ }
+
+ foreach($times as $time) {
+
+ if ($this->end && $time[0] > $this->end) break;
+ if ($this->start && $time[1] < $this->start) break;
+
+ $busyTimes[] = array(
+ $time[0],
+ $time[1],
+ $FBTYPE,
+ );
+ }
+ break;
+
+ case 'VFREEBUSY' :
+ foreach($component->FREEBUSY as $freebusy) {
+
+ $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
+
+ // Skipping intervals marked as 'free'
+ if ($fbType==='FREE')
+ continue;
+
+ $values = explode(',', $freebusy);
+ foreach($values as $value) {
+ list($startTime, $endTime) = explode('/', $value);
+ $startTime = DateTimeParser::parseDateTime($startTime);
+
+ if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
+ $duration = DateTimeParser::parseDuration($endTime);
+ $endTime = clone $startTime;
+ $endTime->add($duration);
+ } else {
+ $endTime = DateTimeParser::parseDateTime($endTime);
+ }
+
+ if($this->start && $this->start > $endTime) continue;
+ if($this->end && $this->end < $startTime) continue;
+ $busyTimes[] = array(
+ $startTime,
+ $endTime,
+ $fbType
+ );
+
+ }
+
+
+ }
+ break;
+
+
+
+ }
+
+
+ }
+
+ }
+
+ if ($this->baseObject) {
+ $calendar = $this->baseObject;
+ } else {
+ $calendar = new VCalendar();
+ }
+
+ $vfreebusy = $calendar->createComponent('VFREEBUSY');
+ $calendar->add($vfreebusy);
+
+ if ($this->start) {
+ $dtstart = $calendar->createProperty('DTSTART');
+ $dtstart->setDateTime($this->start);
+ $vfreebusy->add($dtstart);
+ }
+ if ($this->end) {
+ $dtend = $calendar->createProperty('DTEND');
+ $dtend->setDateTime($this->end);
+ $vfreebusy->add($dtend);
+ }
+ $dtstamp = $calendar->createProperty('DTSTAMP');
+ $dtstamp->setDateTime(new \DateTime('now', new \DateTimeZone('UTC')));
+ $vfreebusy->add($dtstamp);
+
+ foreach($busyTimes as $busyTime) {
+
+ $busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
+ $busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
+
+ $prop = $calendar->createProperty(
+ 'FREEBUSY',
+ $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
+ );
+ $prop['FBTYPE'] = $busyTime[2];
+ $vfreebusy->add($prop);
+
+ }
+
+ return $calendar;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/ITip/Broker.php b/vendor/sabre/vobject/lib/ITip/Broker.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/ITip/Broker.php
@@ -0,0 +1,981 @@
+component !== 'VEVENT') {
+ return false;
+ }
+
+ switch($itipMessage->method) {
+
+ case 'REQUEST' :
+ return $this->processMessageRequest($itipMessage, $existingObject);
+
+ case 'CANCEL' :
+ return $this->processMessageCancel($itipMessage, $existingObject);
+
+ case 'REPLY' :
+ return $this->processMessageReply($itipMessage, $existingObject);
+
+ default :
+ // Unsupported iTip message
+ return null;
+
+ }
+
+ return $existingObject;
+
+ }
+
+ /**
+ * This function parses a VCALENDAR object and figure out if any messages
+ * need to be sent.
+ *
+ * A VCALENDAR object will be created from the perspective of either an
+ * attendee, or an organizer. You must pass a string identifying the
+ * current user, so we can figure out who in the list of attendees or the
+ * organizer we are sending this message on behalf of.
+ *
+ * It's possible to specify the current user as an array, in case the user
+ * has more than one identifying href (such as multiple emails).
+ *
+ * It $oldCalendar is specified, it is assumed that the operation is
+ * updating an existing event, which means that we need to look at the
+ * differences between events, and potentially send old attendees
+ * cancellations, and current attendees updates.
+ *
+ * If $calendar is null, but $oldCalendar is specified, we treat the
+ * operation as if the user has deleted an event. If the user was an
+ * organizer, this means that we need to send cancellation notices to
+ * people. If the user was an attendee, we need to make sure that the
+ * organizer gets the 'declined' message.
+ *
+ * @param VCalendar|string $calendar
+ * @param string|array $userHref
+ * @param VCalendar|string $oldCalendar
+ * @return array
+ */
+ public function parseEvent($calendar = null, $userHref, $oldCalendar = null) {
+
+ if ($oldCalendar) {
+ if (is_string($oldCalendar)) {
+ $oldCalendar = Reader::read($oldCalendar);
+ }
+ if (!isset($oldCalendar->VEVENT)) {
+ // We only support events at the moment
+ return array();
+ }
+
+ $oldEventInfo = $this->parseEventInfo($oldCalendar);
+ } else {
+ $oldEventInfo = array(
+ 'organizer' => null,
+ 'significantChangeHash' => '',
+ 'attendees' => array(),
+ );
+ }
+
+ $userHref = (array)$userHref;
+
+ if (!is_null($calendar)) {
+
+ if (is_string($calendar)) {
+ $calendar = Reader::read($calendar);
+ }
+ if (!isset($calendar->VEVENT)) {
+ // We only support events at the moment
+ return array();
+ }
+ $eventInfo = $this->parseEventInfo($calendar);
+ if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
+ // If there were no attendees on either side of the equation,
+ // we don't need to do anything.
+ return array();
+ }
+ if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
+ // There was no organizer before or after the change.
+ return array();
+ }
+
+ $baseCalendar = $calendar;
+
+ // If the new object didn't have an organizer, the organizer
+ // changed the object from a scheduling object to a non-scheduling
+ // object. We just copy the info from the old object.
+ if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
+ $eventInfo['organizer'] = $oldEventInfo['organizer'];
+ $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
+ }
+
+ } else {
+ // The calendar object got deleted, we need to process this as a
+ // cancellation / decline.
+ if (!$oldCalendar) {
+ // No old and no new calendar, there's no thing to do.
+ return array();
+ }
+
+ $eventInfo = $oldEventInfo;
+
+ if (in_array($eventInfo['organizer'], $userHref)) {
+ // This is an organizer deleting the event.
+ $eventInfo['attendees'] = array();
+ // Increasing the sequence, but only if the organizer deleted
+ // the event.
+ $eventInfo['sequence']++;
+ } else {
+ // This is an attendee deleting the event.
+ foreach($eventInfo['attendees'] as $key=>$attendee) {
+ if (in_array($attendee['href'], $userHref)) {
+ $eventInfo['attendees'][$key]['instances'] = array('master' =>
+ array('id'=>'master', 'partstat' => 'DECLINED')
+ );
+ }
+ }
+ }
+ $baseCalendar = $oldCalendar;
+
+ }
+
+ if (in_array($eventInfo['organizer'], $userHref)) {
+ return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
+ } elseif ($oldCalendar) {
+ // We need to figure out if the user is an attendee, but we're only
+ // doing so if there's an oldCalendar, because we only want to
+ // process updates, not creation of new events.
+ foreach($eventInfo['attendees'] as $attendee) {
+ if (in_array($attendee['href'], $userHref)) {
+ return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
+ }
+ }
+ }
+ return array();
+
+ }
+
+ /**
+ * Processes incoming REQUEST messages.
+ *
+ * This is message from an organizer, and is either a new event
+ * invite, or an update to an existing one.
+ *
+ *
+ * @param Message $itipMessage
+ * @param VCalendar $existingObject
+ * @return VCalendar|null
+ */
+ protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {
+
+ if (!$existingObject) {
+ // This is a new invite, and we're just going to copy over
+ // all the components from the invite.
+ $existingObject = new VCalendar();
+ foreach($itipMessage->message->getComponents() as $component) {
+ $existingObject->add(clone $component);
+ }
+ } else {
+ // We need to update an existing object with all the new
+ // information. We can just remove all existing components
+ // and create new ones.
+ foreach($existingObject->getComponents() as $component) {
+ $existingObject->remove($component);
+ }
+ foreach($itipMessage->message->getComponents() as $component) {
+ $existingObject->add(clone $component);
+ }
+ }
+ return $existingObject;
+
+ }
+
+ /**
+ * Processes incoming CANCEL messages.
+ *
+ * This is a message from an organizer, and means that either an
+ * attendee got removed from an event, or an event got cancelled
+ * altogether.
+ *
+ * @param Message $itipMessage
+ * @param VCalendar $existingObject
+ * @return VCalendar|null
+ */
+ protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {
+
+ if (!$existingObject) {
+ // The event didn't exist in the first place, so we're just
+ // ignoring this message.
+ } else {
+ foreach($existingObject->VEVENT as $vevent) {
+ $vevent->STATUS = 'CANCELLED';
+ $vevent->SEQUENCE = $itipMessage->sequence;
+ }
+ }
+ return $existingObject;
+
+ }
+
+ /**
+ * Processes incoming REPLY messages.
+ *
+ * The message is a reply. This is for example an attendee telling
+ * an organizer he accepted the invite, or declined it.
+ *
+ * @param Message $itipMessage
+ * @param VCalendar $existingObject
+ * @return VCalendar|null
+ */
+ protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {
+
+ // A reply can only be processed based on an existing object.
+ // If the object is not available, the reply is ignored.
+ if (!$existingObject) {
+ return null;
+ }
+ $instances = array();
+ $requestStatus = '2.0';
+
+ // Finding all the instances the attendee replied to.
+ foreach($itipMessage->message->VEVENT as $vevent) {
+ $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master';
+ $attendee = $vevent->ATTENDEE;
+ $instances[$recurId] = $attendee['PARTSTAT']->getValue();
+ if (isset($vevent->{'REQUEST-STATUS'})) {
+ $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
+ list($requestStatus) = explode(';', $requestStatus);
+ }
+ }
+
+ // Now we need to loop through the original organizer event, to find
+ // all the instances where we have a reply for.
+ $masterObject = null;
+ foreach($existingObject->VEVENT as $vevent) {
+ $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master';
+ if ($recurId==='master') {
+ $masterObject = $vevent;
+ }
+ if (isset($instances[$recurId])) {
+ $attendeeFound = false;
+ if (isset($vevent->ATTENDEE)) {
+ foreach($vevent->ATTENDEE as $attendee) {
+ if ($attendee->getValue() === $itipMessage->sender) {
+ $attendeeFound = true;
+ $attendee['PARTSTAT'] = $instances[$recurId];
+ $attendee['SCHEDULE-STATUS'] = $requestStatus;
+ // Un-setting the RSVP status, because we now know
+ // that the attende already replied.
+ unset($attendee['RSVP']);
+ break;
+ }
+ }
+ }
+ if (!$attendeeFound) {
+ // Adding a new attendee. The iTip documentation calls this
+ // a party crasher.
+ $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array(
+ 'PARTSTAT' => $instances[$recurId]
+ ));
+ if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
+ }
+ unset($instances[$recurId]);
+ }
+ }
+
+ if(!$masterObject) {
+ // No master object, we can't add new instances.
+ return null;
+ }
+ // If we got replies to instances that did not exist in the
+ // original list, it means that new exceptions must be created.
+ foreach($instances as $recurId=>$partstat) {
+
+ $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
+ $found = false;
+ $iterations = 1000;
+ do {
+
+ $newObject = $recurrenceIterator->getEventObject();
+ $recurrenceIterator->next();
+
+ if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) {
+ $found = true;
+ }
+ $iterations--;
+
+ } while($recurrenceIterator->valid() && !$found && $iterations);
+
+ // Invalid recurrence id. Skipping this object.
+ if (!$found) continue;
+
+ unset(
+ $newObject->RRULE,
+ $newObject->EXDATE,
+ $newObject->RDATE
+ );
+ $attendeeFound = false;
+ if (isset($newObject->ATTENDEE)) {
+ foreach($newObject->ATTENDEE as $attendee) {
+ if ($attendee->getValue() === $itipMessage->sender) {
+ $attendeeFound = true;
+ $attendee['PARTSTAT'] = $partstat;
+ break;
+ }
+ }
+ }
+ if (!$attendeeFound) {
+ // Adding a new attendee
+ $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array(
+ 'PARTSTAT' => $partstat
+ ));
+ if ($itipMessage->senderName) {
+ $attendee['CN'] = $itipMessage->senderName;
+ }
+ }
+ $existingObject->add($newObject);
+
+ }
+ return $existingObject;
+
+ }
+
+ /**
+ * This method is used in cases where an event got updated, and we
+ * potentially need to send emails to attendees to let them know of updates
+ * in the events.
+ *
+ * We will detect which attendees got added, which got removed and create
+ * specific messages for these situations.
+ *
+ * @param VCalendar $calendar
+ * @param array $eventInfo
+ * @param array $oldEventInfo
+ * @return array
+ */
+ protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
+
+ // Merging attendee lists.
+ $attendees = array();
+ foreach($oldEventInfo['attendees'] as $attendee) {
+ $attendees[$attendee['href']] = array(
+ 'href' => $attendee['href'],
+ 'oldInstances' => $attendee['instances'],
+ 'newInstances' => array(),
+ 'name' => $attendee['name'],
+ 'forceSend' => null,
+ );
+ }
+ foreach($eventInfo['attendees'] as $attendee) {
+ if (isset($attendees[$attendee['href']])) {
+ $attendees[$attendee['href']]['name'] = $attendee['name'];
+ $attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
+ $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
+ } else {
+ $attendees[$attendee['href']] = array(
+ 'href' => $attendee['href'],
+ 'oldInstances' => array(),
+ 'newInstances' => $attendee['instances'],
+ 'name' => $attendee['name'],
+ 'forceSend' => $attendee['forceSend'],
+ );
+ }
+ }
+
+ $messages = array();
+
+ foreach($attendees as $attendee) {
+
+ // An organizer can also be an attendee. We should not generate any
+ // messages for those.
+ if ($attendee['href']===$eventInfo['organizer']) {
+ continue;
+ }
+
+ $message = new Message();
+ $message->uid = $eventInfo['uid'];
+ $message->component = 'VEVENT';
+ $message->sequence = $eventInfo['sequence'];
+ $message->sender = $eventInfo['organizer'];
+ $message->senderName = $eventInfo['organizerName'];
+ $message->recipient = $attendee['href'];
+ $message->recipientName = $attendee['name'];
+
+ if (!$attendee['newInstances']) {
+
+ // If there are no instances the attendee is a part of, it
+ // means the attendee was removed and we need to send him a
+ // CANCEL.
+ $message->method = 'CANCEL';
+
+ // Creating the new iCalendar body.
+ $icalMsg = new VCalendar();
+ $icalMsg->METHOD = $message->method;
+ $event = $icalMsg->add('VEVENT', array(
+ 'UID' => $message->uid,
+ 'SEQUENCE' => $message->sequence,
+ ));
+ if (isset($calendar->VEVENT->SUMMARY)) {
+ $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
+ }
+ $event->add(clone $calendar->VEVENT->DTSTART);
+ if (isset($calendar->VEVENT->DTEND)) {
+ $event->add(clone $calendar->VEVENT->DTEND);
+ } elseif (isset($calendar->VEVENT->DURATION)) {
+ $event->add(clone $calendar->VEVENT->DURATION);
+ }
+ $org = $event->add('ORGANIZER', $eventInfo['organizer']);
+ if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
+ $event->add('ATTENDEE', $attendee['href'], array(
+ 'CN' => $attendee['name'],
+ ));
+ $message->significantChange = true;
+
+ } else {
+
+ // The attendee gets the updated event body
+ $message->method = 'REQUEST';
+
+ // Creating the new iCalendar body.
+ $icalMsg = new VCalendar();
+ $icalMsg->METHOD = $message->method;
+
+ foreach($calendar->select('VTIMEZONE') as $timezone) {
+ $icalMsg->add(clone $timezone);
+ }
+
+ // We need to find out that this change is significant. If it's
+ // not, systems may opt to not send messages.
+ //
+ // We do this based on the 'significantChangeHash' which is
+ // some value that changes if there's a certain set of
+ // properties changed in the event, or simply if there's a
+ // difference in instances that the attendee is invited to.
+
+ $message->significantChange =
+ $attendee['forceSend'] === 'REQUEST' ||
+ array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
+ $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash'];
+
+ foreach($attendee['newInstances'] as $instanceId => $instanceInfo) {
+
+ $currentEvent = clone $eventInfo['instances'][$instanceId];
+ if ($instanceId === 'master') {
+
+ // We need to find a list of events that the attendee
+ // is not a part of to add to the list of exceptions.
+ $exceptions = array();
+ foreach($eventInfo['instances'] as $instanceId=>$vevent) {
+ if (!isset($attendee['newInstances'][$instanceId])) {
+ $exceptions[] = $instanceId;
+ }
+ }
+
+ // If there were exceptions, we need to add it to an
+ // existing EXDATE property, if it exists.
+ if ($exceptions) {
+ if (isset($currentEvent->EXDATE)) {
+ $currentEvent->EXDATE->setParts(array_merge(
+ $currentEvent->EXDATE->getParts(),
+ $exceptions
+ ));
+ } else {
+ $currentEvent->EXDATE = $exceptions;
+ }
+ }
+
+ // Cleaning up any scheduling information that
+ // shouldn't be sent along.
+ unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
+ unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
+
+ foreach($currentEvent->ATTENDEE as $attendee) {
+ unset($attendee['SCHEDULE-FORCE-SEND']);
+ unset($attendee['SCHEDULE-STATUS']);
+
+ // We're adding PARTSTAT=NEEDS-ACTION to ensure that
+ // iOS shows an "Inbox Item"
+ if (!isset($attendee['PARTSTAT'])) {
+ $attendee['PARTSTAT'] = 'NEEDS-ACTION';
+ }
+
+ }
+
+ }
+
+ $icalMsg->add($currentEvent);
+
+ }
+
+ }
+
+ $message->message = $icalMsg;
+ $messages[] = $message;
+
+ }
+
+ return $messages;
+
+ }
+
+ /**
+ * Parse an event update for an attendee.
+ *
+ * This function figures out if we need to send a reply to an organizer.
+ *
+ * @param VCalendar $calendar
+ * @param array $eventInfo
+ * @param array $oldEventInfo
+ * @param string $attendee
+ * @return Message[]
+ */
+ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {
+
+ if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') {
+ return array();
+ }
+
+ // Don't bother generating messages for events that have already been
+ // cancelled.
+ if ($eventInfo['status']==='CANCELLED') {
+ return array();
+ }
+
+ $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
+ $oldEventInfo['attendees'][$attendee]['instances'] :
+ array();
+
+ $instances = array();
+ foreach($oldInstances as $instance) {
+
+ $instances[$instance['id']] = array(
+ 'id' => $instance['id'],
+ 'oldstatus' => $instance['partstat'],
+ 'newstatus' => null,
+ );
+
+ }
+ foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) {
+
+ if (isset($instances[$instance['id']])) {
+ $instances[$instance['id']]['newstatus'] = $instance['partstat'];
+ } else {
+ $instances[$instance['id']] = array(
+ 'id' => $instance['id'],
+ 'oldstatus' => null,
+ 'newstatus' => $instance['partstat'],
+ );
+ }
+
+ }
+
+ // We need to also look for differences in EXDATE. If there are new
+ // items in EXDATE, it means that an attendee deleted instances of an
+ // event, which means we need to send DECLINED specifically for those
+ // instances.
+ // We only need to do that though, if the master event is not declined.
+ if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
+ foreach($eventInfo['exdate'] as $exDate) {
+
+ if (!in_array($exDate, $oldEventInfo['exdate'])) {
+ if (isset($instances[$exDate])) {
+ $instances[$exDate]['newstatus'] = 'DECLINED';
+ } else {
+ $instances[$exDate] = array(
+ 'id' => $exDate,
+ 'oldstatus' => null,
+ 'newstatus' => 'DECLINED',
+ );
+ }
+ }
+
+ }
+ }
+
+ // Gathering a few extra properties for each instance.
+ foreach($instances as $recurId=>$instanceInfo) {
+
+ if (isset($eventInfo['instances'][$recurId])) {
+ $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
+ } else {
+ $instances[$recurId]['dtstart'] = $recurId;
+ }
+
+ }
+
+ $message = new Message();
+ $message->uid = $eventInfo['uid'];
+ $message->method = 'REPLY';
+ $message->component = 'VEVENT';
+ $message->sequence = $eventInfo['sequence'];
+ $message->sender = $attendee;
+ $message->senderName = $eventInfo['attendees'][$attendee]['name'];
+ $message->recipient = $eventInfo['organizer'];
+ $message->recipientName = $eventInfo['organizerName'];
+
+ $icalMsg = new VCalendar();
+ $icalMsg->METHOD = 'REPLY';
+
+ $hasReply = false;
+
+ foreach($instances as $instance) {
+
+ if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
+ // Skip
+ continue;
+ }
+
+ $event = $icalMsg->add('VEVENT', array(
+ 'UID' => $message->uid,
+ 'SEQUENCE' => $message->sequence,
+ ));
+ $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():'';
+ // Adding properties from the correct source instance
+ if (isset($eventInfo['instances'][$instance['id']])) {
+ $instanceObj = $eventInfo['instances'][$instance['id']];
+ $event->add(clone $instanceObj->DTSTART);
+ if (isset($instanceObj->DTEND)) {
+ $event->add(clone $instanceObj->DTEND);
+ } elseif (isset($instanceObj->DURATION)) {
+ $event->add(clone $instanceObj->DURATION);
+ }
+ if (isset($instanceObj->SUMMARY)) {
+ $event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
+ } elseif ($summary) {
+ $event->add('SUMMARY', $summary);
+ }
+ } else {
+ // This branch of the code is reached, when a reply is
+ // generated for an instance of a recurring event, through the
+ // fact that the instance has disappeared by showing up in
+ // EXDATE
+ $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
+ // Treat is as a DATE field
+ if (strlen($instance['id']) <= 8) {
+ $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE'));
+ } else {
+ $recur = $event->add('DTSTART', $dt);
+ }
+ if ($summary) {
+ $event->add('SUMMARY', $summary);
+ }
+ }
+ if ($instance['id'] !== 'master') {
+ $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
+ // Treat is as a DATE field
+ if (strlen($instance['id']) <= 8) {
+ $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE'));
+ } else {
+ $recur = $event->add('RECURRENCE-ID', $dt);
+ }
+ }
+ $organizer = $event->add('ORGANIZER', $message->recipient);
+ if ($message->recipientName) {
+ $organizer['CN'] = $message->recipientName;
+ }
+ $attendee = $event->add('ATTENDEE', $message->sender, array(
+ 'PARTSTAT' => $instance['newstatus']
+ ));
+ if ($message->senderName) {
+ $attendee['CN'] = $message->senderName;
+ }
+ $hasReply = true;
+
+ }
+
+ if ($hasReply) {
+ $message->message = $icalMsg;
+ return array($message);
+ } else {
+ return array();
+ }
+
+ }
+
+ /**
+ * Returns attendee information and information about instances of an
+ * event.
+ *
+ * Returns an array with the following keys:
+ *
+ * 1. uid
+ * 2. organizer
+ * 3. organizerName
+ * 4. organizerScheduleAgent
+ * 5. organizerForceSend
+ * 6. instances
+ * 7. attendees
+ * 8. sequence
+ * 9. exdate
+ * 10. timezone - strictly the timezone on which the recurrence rule is
+ * based on.
+ * 11. significantChangeHash
+ * 12. status
+ * @param VCalendar $calendar
+ * @return array
+ */
+ protected function parseEventInfo(VCalendar $calendar = null) {
+
+ $uid = null;
+ $organizer = null;
+ $organizerName = null;
+ $organizerForceSend = null;
+ $sequence = null;
+ $timezone = null;
+ $status = null;
+ $organizerScheduleAgent = 'SERVER';
+
+ $significantChangeHash = '';
+
+ // Now we need to collect a list of attendees, and which instances they
+ // are a part of.
+ $attendees = array();
+
+ $instances = array();
+ $exdate = array();
+
+ foreach($calendar->VEVENT as $vevent) {
+
+ if (is_null($uid)) {
+ $uid = $vevent->UID->getValue();
+ } else {
+ if ($uid !== $vevent->UID->getValue()) {
+ throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
+ }
+ }
+
+ if (!isset($vevent->DTSTART)) {
+ throw new ITipException('An event MUST have a DTSTART property.');
+ }
+
+ if (isset($vevent->ORGANIZER)) {
+ if (is_null($organizer)) {
+ $organizer = $vevent->ORGANIZER->getNormalizedValue();
+ $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null;
+ } else {
+ if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
+ throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
+ }
+ }
+ $organizerForceSend =
+ isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
+ strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
+ null;
+ $organizerScheduleAgent =
+ isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
+ strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
+ 'SERVER';
+ }
+ if (is_null($sequence) && isset($vevent->SEQUENCE)) {
+ $sequence = $vevent->SEQUENCE->getValue();
+ }
+ if (isset($vevent->EXDATE)) {
+ foreach ($vevent->select('EXDATE') as $val) {
+ $exdate = array_merge($exdate, $val->getParts());
+ }
+ sort($exdate);
+ }
+ if (isset($vevent->STATUS)) {
+ $status = strtoupper($vevent->STATUS->getValue());
+ }
+
+ $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
+ if (is_null($timezone)) {
+ if ($recurId === 'master') {
+ $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
+ } else {
+ $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
+ }
+ }
+ if(isset($vevent->ATTENDEE)) {
+ foreach($vevent->ATTENDEE as $attendee) {
+
+ if ($this->scheduleAgentServerRules &&
+ isset($attendee['SCHEDULE-AGENT']) &&
+ strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
+ ) {
+ continue;
+ }
+ $partStat =
+ isset($attendee['PARTSTAT']) ?
+ strtoupper($attendee['PARTSTAT']) :
+ 'NEEDS-ACTION';
+
+ $forceSend =
+ isset($attendee['SCHEDULE-FORCE-SEND']) ?
+ strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
+ null;
+
+
+ if (isset($attendees[$attendee->getNormalizedValue()])) {
+ $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array(
+ 'id' => $recurId,
+ 'partstat' => $partStat,
+ 'force-send' => $forceSend,
+ );
+ } else {
+ $attendees[$attendee->getNormalizedValue()] = array(
+ 'href' => $attendee->getNormalizedValue(),
+ 'instances' => array(
+ $recurId => array(
+ 'id' => $recurId,
+ 'partstat' => $partStat,
+ ),
+ ),
+ 'name' => isset($attendee['CN'])?(string)$attendee['CN']:null,
+ 'forceSend' => $forceSend,
+ );
+ }
+
+ }
+ $instances[$recurId] = $vevent;
+
+ }
+
+ foreach($this->significantChangeProperties as $prop) {
+ if (isset($vevent->$prop)) {
+ $propertyValues = $vevent->select($prop);
+
+ $significantChangeHash.=$prop.':';
+
+ if ($prop === 'EXDATE') {
+
+ $significantChangeHash.= implode(',', $exdate).';';
+
+ } else {
+
+ foreach($propertyValues as $val) {
+ $significantChangeHash.= $val->getValue().';';
+ }
+
+ }
+ }
+ }
+
+ }
+ $significantChangeHash = md5($significantChangeHash);
+
+ return compact(
+ 'uid',
+ 'organizer',
+ 'organizerName',
+ 'organizerScheduleAgent',
+ 'organizerForceSend',
+ 'instances',
+ 'attendees',
+ 'sequence',
+ 'exdate',
+ 'timezone',
+ 'significantChangeHash',
+ 'status'
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/ITip/ITipException.php b/vendor/sabre/vobject/lib/ITip/ITipException.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/ITip/ITipException.php
@@ -0,0 +1,15 @@
+scheduleStatus) {
+
+ return false;
+
+ } else {
+
+ list($scheduleStatus) = explode(';', $this->scheduleStatus);
+ return $scheduleStatus;
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php b/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php
@@ -0,0 +1,18 @@
+iterator))
+ return $this->iterator;
+
+ return new ElementList(array($this));
+
+ }
+
+ /**
+ * Sets the overridden iterator
+ *
+ * Note that this is not actually part of the iterator interface
+ *
+ * @param ElementList $iterator
+ * @return void
+ */
+ public function setIterator(ElementList $iterator) {
+
+ $this->iterator = $iterator;
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on)
+ * 2 - An inconsequential issue
+ * 3 - A severe issue.
+ *
+ * @param int $options
+ * @return array
+ */
+ public function validate($options = 0) {
+
+ return array();
+
+ }
+
+ /* }}} */
+
+ /* {{{ Countable interface */
+
+ /**
+ * Returns the number of elements
+ *
+ * @return int
+ */
+ public function count() {
+
+ $it = $this->getIterator();
+ return $it->count();
+
+ }
+
+ /* }}} */
+
+ /* {{{ ArrayAccess Interface */
+
+
+ /**
+ * Checks if an item exists through ArrayAccess.
+ *
+ * This method just forwards the request to the inner iterator
+ *
+ * @param int $offset
+ * @return bool
+ */
+ public function offsetExists($offset) {
+
+ $iterator = $this->getIterator();
+ return $iterator->offsetExists($offset);
+
+ }
+
+ /**
+ * Gets an item through ArrayAccess.
+ *
+ * This method just forwards the request to the inner iterator
+ *
+ * @param int $offset
+ * @return mixed
+ */
+ public function offsetGet($offset) {
+
+ $iterator = $this->getIterator();
+ return $iterator->offsetGet($offset);
+
+ }
+
+ /**
+ * Sets an item through ArrayAccess.
+ *
+ * This method just forwards the request to the inner iterator
+ *
+ * @param int $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value) {
+
+ $iterator = $this->getIterator();
+ $iterator->offsetSet($offset,$value);
+
+ // @codeCoverageIgnoreStart
+ //
+ // This method always throws an exception, so we ignore the closing
+ // brace
+ }
+ // @codeCoverageIgnoreEnd
+
+ /**
+ * Sets an item through ArrayAccess.
+ *
+ * This method just forwards the request to the inner iterator
+ *
+ * @param int $offset
+ * @return void
+ */
+ public function offsetUnset($offset) {
+
+ $iterator = $this->getIterator();
+ $iterator->offsetUnset($offset);
+
+ // @codeCoverageIgnoreStart
+ //
+ // This method always throws an exception, so we ignore the closing
+ // brace
+ }
+ // @codeCoverageIgnoreEnd
+
+ /* }}} */
+}
diff --git a/vendor/sabre/vobject/lib/Parameter.php b/vendor/sabre/vobject/lib/Parameter.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Parameter.php
@@ -0,0 +1,373 @@
+name = strtoupper($name);
+ $this->root = $root;
+ if (is_null($name)) {
+ $this->noName = true;
+ $this->name = static::guessParameterNameByValue($value);
+ }
+
+ // If guessParameterNameByValue() returns an empty string
+ // above, we're actually dealing with a parameter that has no value.
+ // In that case we have to move the value to the name.
+ if ($this->name === '') {
+ $this->noName = false;
+ $this->name = strtoupper($value);
+ } else {
+ $this->setValue($value);
+ }
+
+ }
+
+ /**
+ * Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
+ *
+ * Figuring out what the name should have been. Note that a ton of
+ * these are rather silly in 2014 and would probably rarely be
+ * used, but we like to be complete.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function guessParameterNameByValue($value) {
+ switch(strtoupper($value)) {
+
+ // Encodings
+ case '7-BIT' :
+ case 'QUOTED-PRINTABLE' :
+ case 'BASE64' :
+ $name = 'ENCODING';
+ break;
+
+ // Common types
+ case 'WORK' :
+ case 'HOME' :
+ case 'PREF' :
+
+ // Delivery Label Type
+ case 'DOM' :
+ case 'INTL' :
+ case 'POSTAL' :
+ case 'PARCEL' :
+
+ // Telephone types
+ case 'VOICE' :
+ case 'FAX' :
+ case 'MSG' :
+ case 'CELL' :
+ case 'PAGER' :
+ case 'BBS' :
+ case 'MODEM' :
+ case 'CAR' :
+ case 'ISDN' :
+ case 'VIDEO' :
+
+ // EMAIL types (lol)
+ case 'AOL' :
+ case 'APPLELINK' :
+ case 'ATTMAIL' :
+ case 'CIS' :
+ case 'EWORLD' :
+ case 'INTERNET' :
+ case 'IBMMAIL' :
+ case 'MCIMAIL' :
+ case 'POWERSHARE' :
+ case 'PRODIGY' :
+ case 'TLX' :
+ case 'X400' :
+
+ // Photo / Logo format types
+ case 'GIF' :
+ case 'CGM' :
+ case 'WMF' :
+ case 'BMP' :
+ case 'DIB' :
+ case 'PICT' :
+ case 'TIFF' :
+ case 'PDF ':
+ case 'PS' :
+ case 'JPEG' :
+ case 'MPEG' :
+ case 'MPEG2' :
+ case 'AVI' :
+ case 'QTIME' :
+
+ // Sound Digital Audio Type
+ case 'WAVE' :
+ case 'PCM' :
+ case 'AIFF' :
+
+ // Key types
+ case 'X509' :
+ case 'PGP' :
+ $name = 'TYPE';
+ break;
+
+ // Value types
+ case 'INLINE' :
+ case 'URL' :
+ case 'CONTENT-ID' :
+ case 'CID' :
+ $name = 'VALUE';
+ break;
+
+ default:
+ $name = '';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Updates the current value.
+ *
+ * This may be either a single, or multiple strings in an array.
+ *
+ * @param string|array $value
+ * @return void
+ */
+ public function setValue($value) {
+
+ $this->value = $value;
+
+ }
+
+ /**
+ * Returns the current value
+ *
+ * This method will always return a string, or null. If there were multiple
+ * values, it will automatically concatinate them (separated by comma).
+ *
+ * @return string|null
+ */
+ public function getValue() {
+
+ if (is_array($this->value)) {
+ return implode(',' , $this->value);
+ } else {
+ return $this->value;
+ }
+
+ }
+
+ /**
+ * Sets multiple values for this parameter.
+ *
+ * @param array $value
+ * @return void
+ */
+ public function setParts(array $value) {
+
+ $this->value = $value;
+
+ }
+
+ /**
+ * Returns all values for this parameter.
+ *
+ * If there were no values, an empty array will be returned.
+ *
+ * @return array
+ */
+ public function getParts() {
+
+ if (is_array($this->value)) {
+ return $this->value;
+ } elseif (is_null($this->value)) {
+ return array();
+ } else {
+ return array($this->value);
+ }
+
+ }
+
+ /**
+ * Adds a value to this parameter
+ *
+ * If the argument is specified as an array, all items will be added to the
+ * parameter value list.
+ *
+ * @param string|array $part
+ * @return void
+ */
+ public function addValue($part) {
+
+ if (is_null($this->value)) {
+ $this->value = $part;
+ } else {
+ $this->value = array_merge((array)$this->value, (array)$part);
+ }
+
+ }
+
+ /**
+ * Checks if this parameter contains the specified value.
+ *
+ * This is a case-insensitive match. It makes sense to call this for for
+ * instance the TYPE parameter, to see if it contains a keyword such as
+ * 'WORK' or 'FAX'.
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function has($value) {
+
+ return in_array(
+ strtolower($value),
+ array_map('strtolower', (array)$this->value)
+ );
+
+ }
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ public function serialize() {
+
+ $value = $this->getParts();
+
+ if (count($value)===0) {
+ return $this->name . '=';
+ }
+
+ if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
+
+ return implode(';', $value);
+
+ }
+
+ return $this->name . '=' . array_reduce(
+ $value,
+ function($out, $item) {
+
+ if (!is_null($out)) $out.=',';
+
+ // If there's no special characters in the string, we'll use the simple
+ // format.
+ //
+ // The list of special characters is defined as:
+ //
+ // Any character except CONTROL, DQUOTE, ";", ":", ","
+ //
+ // by the iCalendar spec:
+ // https://tools.ietf.org/html/rfc5545#section-3.1
+ //
+ // And we add ^ to that because of:
+ // https://tools.ietf.org/html/rfc6868
+ //
+ // But we've found that iCal (7.0, shipped with OSX 10.9)
+ // severaly trips on + characters not being quoted, so we
+ // added + as well.
+ if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
+ return $out.$item;
+ } else {
+ // Enclosing in double-quotes, and using RFC6868 for encoding any
+ // special characters
+ $out.='"' . strtr(
+ $item,
+ array(
+ '^' => '^^',
+ "\n" => '^n',
+ '"' => '^\'',
+ )
+ ) . '"';
+ return $out;
+ }
+
+ }
+ );
+
+ }
+
+ /**
+ * This method returns an array, with the representation as it should be
+ * encoded in json. This is used to create jCard or jCal documents.
+ *
+ * @return array
+ */
+ public function jsonSerialize() {
+
+ return $this->value;
+
+ }
+
+ /**
+ * Called when this object is being cast to a string
+ *
+ * @return string
+ */
+ public function __toString() {
+
+ return (string)$this->getValue();
+
+ }
+
+ /**
+ * Returns the iterator for this object
+ *
+ * @return ElementList
+ */
+ public function getIterator() {
+
+ if (!is_null($this->iterator))
+ return $this->iterator;
+
+ return $this->iterator = new ArrayObject((array)$this->value);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/ParseException.php b/vendor/sabre/vobject/lib/ParseException.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/ParseException.php
@@ -0,0 +1,13 @@
+setInput($input);
+ }
+ if (is_null($this->input)) {
+ throw new EofException('End of input stream, or no input supplied');
+ }
+
+ if (!is_null($options)) {
+ $this->options = $options;
+ }
+
+ switch($this->input[0]) {
+ case 'vcalendar' :
+ $this->root = new VCalendar(array(), false);
+ break;
+ case 'vcard' :
+ $this->root = new VCard(array(), false);
+ break;
+ default :
+ throw new ParseException('The root component must either be a vcalendar, or a vcard');
+
+ }
+ foreach($this->input[1] as $prop) {
+ $this->root->add($this->parseProperty($prop));
+ }
+ if (isset($this->input[2])) foreach($this->input[2] as $comp) {
+ $this->root->add($this->parseComponent($comp));
+ }
+
+ // Resetting the input so we can throw an feof exception the next time.
+ $this->input = null;
+
+ return $this->root;
+
+ }
+
+ /**
+ * Parses a component
+ *
+ * @param array $jComp
+ * @return \Sabre\VObject\Component
+ */
+ public function parseComponent(array $jComp) {
+
+ // We can remove $self from PHP 5.4 onward.
+ $self = $this;
+
+ $properties = array_map(
+ function($jProp) use ($self) {
+ return $self->parseProperty($jProp);
+ },
+ $jComp[1]
+ );
+
+ if (isset($jComp[2])) {
+
+ $components = array_map(
+ function($jComp) use ($self) {
+ return $self->parseComponent($jComp);
+ },
+ $jComp[2]
+ );
+
+ } else $components = array();
+
+ return $this->root->createComponent(
+ $jComp[0],
+ array_merge($properties, $components),
+ $defaults = false
+ );
+
+ }
+
+ /**
+ * Parses properties.
+ *
+ * @param array $jProp
+ * @return \Sabre\VObject\Property
+ */
+ public function parseProperty(array $jProp) {
+
+ list(
+ $propertyName,
+ $parameters,
+ $valueType
+ ) = $jProp;
+
+ $propertyName = strtoupper($propertyName);
+
+ // This is the default class we would be using if we didn't know the
+ // value type. We're using this value later in this function.
+ $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);
+
+ $parameters = (array)$parameters;
+
+ $value = array_slice($jProp, 3);
+
+ $valueType = strtoupper($valueType);
+
+ if (isset($parameters['group'])) {
+ $propertyName = $parameters['group'] . '.' . $propertyName;
+ unset($parameters['group']);
+ }
+
+ $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
+ $prop->setJsonValue($value);
+
+ // We have to do something awkward here. FlatText as well as Text
+ // represents TEXT values. We have to normalize these here. In the
+ // future we can get rid of FlatText once we're allowed to break BC
+ // again.
+ if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
+ $defaultPropertyClass = 'Sabre\VObject\Property\Text';
+ }
+
+ // If the value type we received (e.g.: TEXT) was not the default value
+ // type for the given property (e.g.: BDAY), we need to add a VALUE=
+ // parameter.
+ if ($defaultPropertyClass !== get_class($prop)) {
+ $prop["VALUE"] = $valueType;
+ }
+
+ return $prop;
+
+ }
+
+ /**
+ * Sets the input data
+ *
+ * @param resource|string|array $input
+ * @return void
+ */
+ public function setInput($input) {
+
+ if (is_resource($input)) {
+ $input = stream_get_contents($input);
+ }
+ if (is_string($input)) {
+ $input = json_decode($input);
+ }
+ $this->input = $input;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Parser/MimeDir.php b/vendor/sabre/vobject/lib/Parser/MimeDir.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Parser/MimeDir.php
@@ -0,0 +1,628 @@
+root = null;
+ if (!is_null($input)) {
+
+ $this->setInput($input);
+
+ }
+
+ if (!is_null($options)) $this->options = $options;
+
+ $this->parseDocument();
+
+ return $this->root;
+
+ }
+
+ /**
+ * Sets the input buffer. Must be a string or stream.
+ *
+ * @param resource|string $input
+ * @return void
+ */
+ public function setInput($input) {
+
+ // Resetting the parser
+ $this->lineIndex = 0;
+ $this->startLine = 0;
+
+ if (is_string($input)) {
+ // Convering to a stream.
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $input);
+ rewind($stream);
+ $this->input = $stream;
+ } elseif (is_resource($input)) {
+ $this->input = $input;
+ } else {
+ throw new \InvalidArgumentException('This parser can only read from strings or streams.');
+ }
+
+ }
+
+ /**
+ * Parses an entire document.
+ *
+ * @return void
+ */
+ protected function parseDocument() {
+
+ $line = $this->readLine();
+
+ // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
+ // It's 0xEF 0xBB 0xBF in UTF-8 hex.
+ if ( 3 <= strlen($line)
+ && ord($line[0]) === 0xef
+ && ord($line[1]) === 0xbb
+ && ord($line[2]) === 0xbf) {
+ $line = substr($line, 3);
+ }
+
+ switch(strtoupper($line)) {
+ case 'BEGIN:VCALENDAR' :
+ $class = isset(VCalendar::$componentMap['VCALENDAR'])
+ ? VCalendar::$componentMap[$name]
+ : 'Sabre\\VObject\\Component\\VCalendar';
+ break;
+ case 'BEGIN:VCARD' :
+ $class = isset(VCard::$componentMap['VCARD'])
+ ? VCard::$componentMap['VCARD']
+ : 'Sabre\\VObject\\Component\\VCard';
+ break;
+ default :
+ throw new ParseException('This parser only supports VCARD and VCALENDAR files');
+ }
+
+ $this->root = new $class(array(), false);
+
+ while(true) {
+
+ // Reading until we hit END:
+ $line = $this->readLine();
+ if (strtoupper(substr($line,0,4)) === 'END:') {
+ break;
+ }
+ $result = $this->parseLine($line);
+ if ($result) {
+ $this->root->add($result);
+ }
+
+ }
+
+ $name = strtoupper(substr($line, 4));
+ if ($name!==$this->root->name) {
+ throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
+ }
+
+ }
+
+ /**
+ * Parses a line, and if it hits a component, it will also attempt to parse
+ * the entire component
+ *
+ * @param string $line Unfolded line
+ * @return Node
+ */
+ protected function parseLine($line) {
+
+ // Start of a new component
+ if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
+
+ $component = $this->root->createComponent(substr($line,6), array(), false);
+
+ while(true) {
+
+ // Reading until we hit END:
+ $line = $this->readLine();
+ if (strtoupper(substr($line,0,4)) === 'END:') {
+ break;
+ }
+ $result = $this->parseLine($line);
+ if ($result) {
+ $component->add($result);
+ }
+
+ }
+
+ $name = strtoupper(substr($line, 4));
+ if ($name!==$component->name) {
+ throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
+ }
+
+ return $component;
+
+ } else {
+
+ // Property reader
+ $property = $this->readProperty($line);
+ if (!$property) {
+ // Ignored line
+ return false;
+ }
+ return $property;
+
+ }
+
+ }
+
+ /**
+ * We need to look ahead 1 line every time to see if we need to 'unfold'
+ * the next line.
+ *
+ * If that was not the case, we store it here.
+ *
+ * @var null|string
+ */
+ protected $lineBuffer;
+
+ /**
+ * The real current line number.
+ */
+ protected $lineIndex = 0;
+
+ /**
+ * In the case of unfolded lines, this property holds the line number for
+ * the start of the line.
+ *
+ * @var int
+ */
+ protected $startLine = 0;
+
+ /**
+ * Contains a 'raw' representation of the current line.
+ *
+ * @var string
+ */
+ protected $rawLine;
+
+ /**
+ * Reads a single line from the buffer.
+ *
+ * This method strips any newlines and also takes care of unfolding.
+ *
+ * @throws \Sabre\VObject\EofException
+ * @return string
+ */
+ protected function readLine() {
+
+ if (!is_null($this->lineBuffer)) {
+ $rawLine = $this->lineBuffer;
+ $this->lineBuffer = null;
+ } else {
+ do {
+ $eof = feof($this->input);
+
+ $rawLine = fgets($this->input);
+
+ if ($eof || (feof($this->input) && $rawLine===false)) {
+ throw new EofException('End of document reached prematurely');
+ }
+ if ($rawLine === false) {
+ throw new ParseException('Error reading from input stream');
+ }
+ $rawLine = rtrim($rawLine, "\r\n");
+ } while ($rawLine === ''); // Skipping empty lines
+ $this->lineIndex++;
+ }
+ $line = $rawLine;
+
+ $this->startLine = $this->lineIndex;
+
+ // Looking ahead for folded lines.
+ while (true) {
+
+ $nextLine = rtrim(fgets($this->input), "\r\n");
+ $this->lineIndex++;
+ if (!$nextLine) {
+ break;
+ }
+ if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
+ $line .= substr($nextLine, 1);
+ $rawLine .= "\n " . substr($nextLine, 1);
+ } else {
+ $this->lineBuffer = $nextLine;
+ break;
+ }
+
+ }
+ $this->rawLine = $rawLine;
+ return $line;
+
+ }
+
+ /**
+ * Reads a property or component from a line.
+ *
+ * @return void
+ */
+ protected function readProperty($line) {
+
+ if ($this->options & self::OPTION_FORGIVING) {
+ $propNameToken = 'A-Z0-9\-\._\\/';
+ } else {
+ $propNameToken = 'A-Z0-9\-\.';
+ }
+
+ $paramNameToken = 'A-Z0-9\-';
+ $safeChar = '^";:,';
+ $qSafeChar = '^"';
+
+ $regex = "/
+ ^(?P [$propNameToken]+ ) (?=[;:]) # property name
+ |
+ (?<=:)(?P .+)$ # property value
+ |
+ ;(?P [$paramNameToken]+) (?=[=;:]) # parameter name
+ |
+ (=|,)(?P # parameter value
+ (?: [$safeChar]*) |
+ \"(?: [$qSafeChar]+)\"
+ ) (?=[;:,])
+ /xi";
+
+ //echo $regex, "\n"; die();
+ preg_match_all($regex, $line, $matches, PREG_SET_ORDER);
+
+ $property = array(
+ 'name' => null,
+ 'parameters' => array(),
+ 'value' => null
+ );
+
+ $lastParam = null;
+
+ /**
+ * Looping through all the tokens.
+ *
+ * Note that we are looping through them in reverse order, because if a
+ * sub-pattern matched, the subsequent named patterns will not show up
+ * in the result.
+ */
+ foreach($matches as $match) {
+
+ if (isset($match['paramValue'])) {
+ if ($match['paramValue'] && $match['paramValue'][0] === '"') {
+ $value = substr($match['paramValue'], 1, -1);
+ } else {
+ $value = $match['paramValue'];
+ }
+
+ $value = $this->unescapeParam($value);
+
+ if (is_null($property['parameters'][$lastParam])) {
+ $property['parameters'][$lastParam] = $value;
+ } elseif (is_array($property['parameters'][$lastParam])) {
+ $property['parameters'][$lastParam][] = $value;
+ } else {
+ $property['parameters'][$lastParam] = array(
+ $property['parameters'][$lastParam],
+ $value
+ );
+ }
+ continue;
+ }
+ if (isset($match['paramName'])) {
+ $lastParam = strtoupper($match['paramName']);
+ if (!isset($property['parameters'][$lastParam])) {
+ $property['parameters'][$lastParam] = null;
+ }
+ continue;
+ }
+ if (isset($match['propValue'])) {
+ $property['value'] = $match['propValue'];
+ continue;
+ }
+ if (isset($match['name']) && $match['name']) {
+ $property['name'] = strtoupper($match['name']);
+ continue;
+ }
+
+ // @codeCoverageIgnoreStart
+ throw new \LogicException('This code should not be reachable');
+ // @codeCoverageIgnoreEnd
+
+ }
+
+ if (is_null($property['value'])) {
+ $property['value'] = '';
+ }
+ if (!$property['name']) {
+ if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
+ return false;
+ }
+ throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
+ }
+
+ // vCard 2.1 states that parameters may appear without a name, and only
+ // a value. We can deduce the value based on it's name.
+ //
+ // Our parser will get those as parameters without a value instead, so
+ // we're filtering these parameters out first.
+ $namedParameters = array();
+ $namelessParameters = array();
+
+ foreach($property['parameters'] as $name=>$value) {
+ if (!is_null($value)) {
+ $namedParameters[$name] = $value;
+ } else {
+ $namelessParameters[] = $name;
+ }
+ }
+
+ $propObj = $this->root->createProperty($property['name'], null, $namedParameters);
+
+ foreach($namelessParameters as $namelessParameter) {
+ $propObj->add(null, $namelessParameter);
+ }
+
+ if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
+ $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
+ } else {
+ $propObj->setRawMimeDirValue($property['value']);
+ }
+
+ return $propObj;
+
+ }
+
+ /**
+ * Unescapes a property value.
+ *
+ * vCard 2.1 says:
+ * * Semi-colons must be escaped in some property values, specifically
+ * ADR, ORG and N.
+ * * Semi-colons must be escaped in parameter values, because semi-colons
+ * are also use to separate values.
+ * * No mention of escaping backslashes with another backslash.
+ * * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
+ * span values over more than 1 line.
+ *
+ * vCard 3.0 says:
+ * * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
+ * escaped, all time time.
+ * * Comma's are used for delimeters in multiple values
+ * * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
+ * as in some properties semi-colon is used for separators.
+ * * Properties using semi-colons: N, ADR, GEO, ORG
+ * * Both ADR and N's individual parts may be broken up further with a
+ * comma.
+ * * Properties using commas: NICKNAME, CATEGORIES
+ *
+ * vCard 4.0 (rfc6350) says:
+ * * Commas must be escaped.
+ * * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
+ * delimiter, depending on the property.
+ * * Backslashes must be escaped
+ * * Newlines must be escaped as either \N or \n.
+ * * Some compound properties may contain multiple parts themselves, so a
+ * comma within a semi-colon delimited property may also be unescaped
+ * to denote multiple parts _within_ the compound property.
+ * * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
+ * * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
+ *
+ * Even though the spec says that commas must always be escaped, the
+ * example for GEO in Section 6.5.2 seems to violate this.
+ *
+ * iCalendar 2.0 (rfc5545) says:
+ * * Commas or semi-colons may be used as delimiters, depending on the
+ * property.
+ * * Commas, semi-colons, backslashes, newline (\N or \n) are always
+ * escaped, unless they are delimiters.
+ * * Colons shall not be escaped.
+ * * Commas can be considered the 'default delimiter' and is described as
+ * the delimiter in cases where the order of the multiple values is
+ * insignificant.
+ * * Semi-colons are described as the delimiter for 'structured values'.
+ * They are specifically used in Semi-colons are used as a delimiter in
+ * REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
+ *
+ * Now for the parameters
+ *
+ * If delimiter is not set (null) this method will just return a string.
+ * If it's a comma or a semi-colon the string will be split on those
+ * characters, and always return an array.
+ *
+ * @param string $input
+ * @param string $delimiter
+ * @return string|string[]
+ */
+ static public function unescapeValue($input, $delimiter = ';') {
+
+ $regex = '# (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
+ if ($delimiter) {
+ $regex .= ' | (' . $delimiter . ')';
+ }
+ $regex .= ') #x';
+
+ $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+ $resultArray = array();
+ $result = '';
+
+ foreach($matches as $match) {
+
+ switch ($match) {
+ case '\\\\' :
+ $result .='\\';
+ break;
+ case '\N' :
+ case '\n' :
+ $result .="\n";
+ break;
+ case '\;' :
+ $result .=';';
+ break;
+ case '\,' :
+ $result .=',';
+ break;
+ case $delimiter :
+ $resultArray[] = $result;
+ $result = '';
+ break;
+ default :
+ $result .= $match;
+ break;
+
+ }
+
+ }
+
+ $resultArray[] = $result;
+ return $delimiter ? $resultArray : $result;
+
+ }
+
+ /**
+ * Unescapes a parameter value.
+ *
+ * vCard 2.1:
+ * * Does not mention a mechanism for this. In addition, double quotes
+ * are never used to wrap values.
+ * * This means that parameters can simply not contain colons or
+ * semi-colons.
+ *
+ * vCard 3.0 (rfc2425, rfc2426):
+ * * Parameters _may_ be surrounded by double quotes.
+ * * If this is not the case, semi-colon, colon and comma may simply not
+ * occur (the comma used for multiple parameter values though).
+ * * If it is surrounded by double-quotes, it may simply not contain
+ * double-quotes.
+ * * This means that a parameter can in no case encode double-quotes, or
+ * newlines.
+ *
+ * vCard 4.0 (rfc6350)
+ * * Behavior seems to be identical to vCard 3.0
+ *
+ * iCalendar 2.0 (rfc5545)
+ * * Behavior seems to be identical to vCard 3.0
+ *
+ * Parameter escaping mechanism (rfc6868) :
+ * * This rfc describes a new way to escape parameter values.
+ * * New-line is encoded as ^n
+ * * ^ is encoded as ^^.
+ * * " is encoded as ^'
+ *
+ * @param string $input
+ * @return void
+ */
+ private function unescapeParam($input) {
+
+ return
+ preg_replace_callback(
+ '#(\^(\^|n|\'))#',
+ function($matches) {
+ switch($matches[2]) {
+ case 'n' :
+ return "\n";
+ case '^' :
+ return '^';
+ case '\'' :
+ return '"';
+
+ // @codeCoverageIgnoreStart
+ }
+ // @codeCoverageIgnoreEnd
+ },
+ $input
+ );
+ }
+
+ /**
+ * Gets the full quoted printable value.
+ *
+ * We need a special method for this, because newlines have both a meaning
+ * in vCards, and in QuotedPrintable.
+ *
+ * This method does not do any decoding.
+ *
+ * @return string
+ */
+ private function extractQuotedPrintableValue() {
+
+ // We need to parse the raw line again to get the start of the value.
+ //
+ // We are basically looking for the first colon (:), but we need to
+ // skip over the parameters first, as they may contain one.
+ $regex = '/^
+ (?: [^:])+ # Anything but a colon
+ (?: "[^"]")* # A parameter in double quotes
+ : # start of the value we really care about
+ (.*)$
+ /xs';
+
+ preg_match($regex, $this->rawLine, $matches);
+
+ $value = $matches[1];
+ // Removing the first whitespace character from every line. Kind of
+ // like unfolding, but we keep the newline.
+ $value = str_replace("\n ", "\n", $value);
+
+ // Microsoft products don't always correctly fold lines, they may be
+ // missing a whitespace. So if 'forgiving' is turned on, we will take
+ // those as well.
+ if ($this->options & self::OPTION_FORGIVING) {
+ while(substr($value,-1) === '=') {
+ // Reading the line
+ $this->readLine();
+ // Grabbing the raw form
+ $value.="\n" . $this->rawLine;
+ }
+ }
+
+ return $value;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Parser/Parser.php b/vendor/sabre/vobject/lib/Parser/Parser.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Parser/Parser.php
@@ -0,0 +1,77 @@
+setInput($input);
+ }
+ $this->options = $options;
+ }
+
+ /**
+ * This method starts the parsing process.
+ *
+ * If the input was not supplied during construction, it's possible to pass
+ * it here instead.
+ *
+ * If either input or options are not supplied, the defaults will be used.
+ *
+ * @param mixed $input
+ * @param int|null $options
+ * @return array
+ */
+ abstract public function parse($input = null, $options = null);
+
+ /**
+ * Sets the input data
+ *
+ * @param mixed $input
+ * @return void
+ */
+ abstract public function setInput($input);
+
+}
diff --git a/vendor/sabre/vobject/lib/Property.php b/vendor/sabre/vobject/lib/Property.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property.php
@@ -0,0 +1,555 @@
+value syntax.
+ *
+ * @param Component $root The root document
+ * @param string $name
+ * @param string|array|null $value
+ * @param array $parameters List of parameters
+ * @param string $group The vcard property group
+ * @return void
+ */
+ function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
+
+ $this->name = $name;
+ $this->group = $group;
+
+ $this->root = $root;
+
+ foreach($parameters as $k=>$v) {
+ $this->add($k, $v);
+ }
+
+ if (!is_null($value)) {
+ $this->setValue($value);
+ }
+
+ }
+
+ /**
+ * Updates the current value.
+ *
+ * This may be either a single, or multiple strings in an array.
+ *
+ * @param string|array $value
+ * @return void
+ */
+ function setValue($value) {
+
+ $this->value = $value;
+
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * This method will always return a singular value. If this was a
+ * multi-value object, some decision will be made first on how to represent
+ * it as a string.
+ *
+ * To get the correct multi-value version, use getParts.
+ *
+ * @return string
+ */
+ function getValue() {
+
+ if (is_array($this->value)) {
+ if (count($this->value)==0) {
+ return null;
+ } elseif (count($this->value)===1) {
+ return $this->value[0];
+ } else {
+ return $this->getRawMimeDirValue($this->value);
+ }
+ } else {
+ return $this->value;
+ }
+
+ }
+
+ /**
+ * Sets a multi-valued property.
+ *
+ * @param array $parts
+ * @return void
+ */
+ function setParts(array $parts) {
+
+ $this->value = $parts;
+
+ }
+
+ /**
+ * Returns a multi-valued property.
+ *
+ * This method always returns an array, if there was only a single value,
+ * it will still be wrapped in an array.
+ *
+ * @return array
+ */
+ function getParts() {
+
+ if (is_null($this->value)) {
+ return array();
+ } elseif (is_array($this->value)) {
+ return $this->value;
+ } else {
+ return array($this->value);
+ }
+
+ }
+
+ /**
+ * Adds a new parameter, and returns the new item.
+ *
+ * If a parameter with same name already existed, the values will be
+ * combined.
+ * If nameless parameter is added, we try to guess it's name.
+ *
+ * @param string $name
+ * @param string|null|array $value
+ * @return Node
+ */
+ function add($name, $value = null) {
+ $noName = false;
+ if ($name === null) {
+ $name = Parameter::guessParameterNameByValue($value);
+ $noName = true;
+ }
+
+ if (isset($this->parameters[strtoupper($name)])) {
+ $this->parameters[strtoupper($name)]->addValue($value);
+ }
+ else {
+ $param = new Parameter($this->root, $name, $value);
+ $param->noName = $noName;
+ $this->parameters[$param->name] = $param;
+ }
+ }
+
+ /**
+ * Returns an iterable list of children
+ *
+ * @return array
+ */
+ function parameters() {
+
+ return $this->parameters;
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ abstract function getValueType();
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ abstract function setRawMimeDirValue($val);
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ abstract function getRawMimeDirValue();
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ function serialize() {
+
+ $str = $this->name;
+ if ($this->group) $str = $this->group . '.' . $this->name;
+
+ foreach($this->parameters as $param) {
+
+ $str.=';' . $param->serialize();
+
+ }
+
+ $str.=':' . $this->getRawMimeDirValue();
+
+ $out = '';
+ while(strlen($str)>0) {
+ if (strlen($str)>75) {
+ $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
+ $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
+ } else {
+ $out.=$str . "\r\n";
+ $str='';
+ break;
+ }
+ }
+
+ return $out;
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ function getJsonValue() {
+
+ return $this->getParts();
+
+ }
+
+ /**
+ * Sets the json value, as it would appear in a jCard or jCal object.
+ *
+ * The value must always be an array.
+ *
+ * @param array $value
+ * @return void
+ */
+ function setJsonValue(array $value) {
+
+ if (count($value)===1) {
+ $this->setValue(reset($value));
+ } else {
+ $this->setValue($value);
+ }
+
+ }
+
+ /**
+ * This method returns an array, with the representation as it should be
+ * encoded in json. This is used to create jCard or jCal documents.
+ *
+ * @return array
+ */
+ function jsonSerialize() {
+
+ $parameters = array();
+
+ foreach($this->parameters as $parameter) {
+ if ($parameter->name === 'VALUE') {
+ continue;
+ }
+ $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
+ }
+ // In jCard, we need to encode the property-group as a separate 'group'
+ // parameter.
+ if ($this->group) {
+ $parameters['group'] = $this->group;
+ }
+
+ return array_merge(
+ array(
+ strtolower($this->name),
+ (object)$parameters,
+ strtolower($this->getValueType()),
+ ),
+ $this->getJsonValue()
+ );
+ }
+
+
+ /**
+ * Called when this object is being cast to a string.
+ *
+ * If the property only had a single value, you will get just that. In the
+ * case the property had multiple values, the contents will be escaped and
+ * combined with ,.
+ *
+ * @return string
+ */
+ function __toString() {
+
+ return (string)$this->getValue();
+
+ }
+
+ /* ArrayAccess interface {{{ */
+
+ /**
+ * Checks if an array element exists
+ *
+ * @param mixed $name
+ * @return bool
+ */
+ function offsetExists($name) {
+
+ if (is_int($name)) return parent::offsetExists($name);
+
+ $name = strtoupper($name);
+
+ foreach($this->parameters as $parameter) {
+ if ($parameter->name == $name) return true;
+ }
+ return false;
+
+ }
+
+ /**
+ * Returns a parameter.
+ *
+ * If the parameter does not exist, null is returned.
+ *
+ * @param string $name
+ * @return Node
+ */
+ function offsetGet($name) {
+
+ if (is_int($name)) return parent::offsetGet($name);
+ $name = strtoupper($name);
+
+ if (!isset($this->parameters[$name])) {
+ return null;
+ }
+
+ return $this->parameters[$name];
+
+ }
+
+ /**
+ * Creates a new parameter
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ function offsetSet($name, $value) {
+
+ if (is_int($name)) {
+ parent::offsetSet($name, $value);
+ // @codeCoverageIgnoreStart
+ // This will never be reached, because an exception is always
+ // thrown.
+ return;
+ // @codeCoverageIgnoreEnd
+ }
+
+ $param = new Parameter($this->root, $name, $value);
+ $this->parameters[$param->name] = $param;
+
+ }
+
+ /**
+ * Removes one or more parameters with the specified name
+ *
+ * @param string $name
+ * @return void
+ */
+ function offsetUnset($name) {
+
+ if (is_int($name)) {
+ parent::offsetUnset($name);
+ // @codeCoverageIgnoreStart
+ // This will never be reached, because an exception is always
+ // thrown.
+ return;
+ // @codeCoverageIgnoreEnd
+ }
+
+ unset($this->parameters[strtoupper($name)]);
+
+ }
+ /* }}} */
+
+ /**
+ * This method is automatically called when the object is cloned.
+ * Specifically, this will ensure all child elements are also cloned.
+ *
+ * @return void
+ */
+ function __clone() {
+
+ foreach($this->parameters as $key=>$child) {
+ $this->parameters[$key] = clone $child;
+ $this->parameters[$key]->parent = $this;
+ }
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * - Node::REPAIR - If something is broken, and automatic repair may
+ * be attempted.
+ *
+ * An array is returned with warnings.
+ *
+ * Every item in the array has the following properties:
+ * * level - (number between 1 and 3 with severity information)
+ * * message - (human readable message)
+ * * node - (reference to the offending node)
+ *
+ * @param int $options
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $warnings = array();
+
+ // Checking if our value is UTF-8
+ if (!StringUtil::isUTF8($this->getRawMimeDirValue())) {
+
+ $oldValue = $this->getRawMimeDirValue();
+ $level = 3;
+ if ($options & self::REPAIR) {
+ $newValue = StringUtil::convertToUTF8($oldValue);
+ if (true || StringUtil::isUTF8($newValue)) {
+ $this->setRawMimeDirValue($newValue);
+ $level = 1;
+ }
+
+ }
+
+
+ if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) {
+ $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')';
+ } else {
+ $message = 'Property is not valid UTF-8! ' . $oldValue;
+ }
+
+ $warnings[] = array(
+ 'level' => $level,
+ 'message' => $message,
+ 'node' => $this,
+ );
+ }
+
+ // Checking if the propertyname does not contain any invalid bytes.
+ if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
+ $warnings[] = array(
+ 'level' => 1,
+ 'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
+ 'node' => $this,
+ );
+ if ($options & self::REPAIR) {
+ // Uppercasing and converting underscores to dashes.
+ $this->name = strtoupper(
+ str_replace('_', '-', $this->name)
+ );
+ // Removing every other invalid character
+ $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);
+
+ }
+
+ }
+
+ if ($encoding = $this->offsetGet('ENCODING')) {
+
+ if ($this->root->getDocumentType()===Document::VCARD40) {
+ $warnings[] = array(
+ 'level' => 1,
+ 'message' => 'ENCODING parameter is not valid in vCard 4.',
+ 'node' => $this
+ );
+ } else {
+
+ $encoding = (string)$encoding;
+
+ $allowedEncoding = array();
+
+ switch($this->root->getDocumentType()) {
+ case Document::ICALENDAR20 :
+ $allowedEncoding = array('8BIT', 'BASE64');
+ break;
+ case Document::VCARD21 :
+ $allowedEncoding = array('QUOTED-PRINTABLE', 'BASE64', '8BIT');
+ break;
+ case Document::VCARD30 :
+ $allowedEncoding = array('B');
+ break;
+
+ }
+ if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
+ $warnings[] = array(
+ 'level' => 1,
+ 'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.',
+ 'node' => $this
+ );
+ }
+ }
+
+ }
+
+ // Validating inner parameters
+ foreach($this->parameters as $param) {
+ $warnings = array_merge($warnings, $param->validate($options));
+ }
+
+ return $warnings;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/Binary.php b/vendor/sabre/vobject/lib/Property/Binary.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Binary.php
@@ -0,0 +1,127 @@
+value = $value[0];
+ } else {
+ throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
+ }
+
+ } else {
+
+ $this->value = $value;
+
+ }
+
+ }
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setRawMimeDirValue($val) {
+
+ $this->value = base64_decode($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return base64_encode($this->value);
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return 'BINARY';
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ return array(base64_encode($this->getValue()));
+
+ }
+
+ /**
+ * Sets the json value, as it would appear in a jCard or jCal object.
+ *
+ * The value must always be an array.
+ *
+ * @param array $value
+ * @return void
+ */
+ public function setJsonValue(array $value) {
+
+ $value = array_map('base64_decode', $value);
+ parent::setJsonValue($value);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/Boolean.php b/vendor/sabre/vobject/lib/Property/Boolean.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Boolean.php
@@ -0,0 +1,63 @@
+setValue($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return $this->value?'TRUE':'FALSE';
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return 'BOOLEAN';
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/FlatText.php b/vendor/sabre/vobject/lib/Property/FlatText.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/FlatText.php
@@ -0,0 +1,49 @@
+setValue($val);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/FloatValue.php b/vendor/sabre/vobject/lib/Property/FloatValue.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/FloatValue.php
@@ -0,0 +1,104 @@
+delimiter, $val);
+ foreach($val as &$item) {
+ $item = (float)$item;
+ }
+ $this->setParts($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return implode(
+ $this->delimiter,
+ $this->getParts()
+ );
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "FLOAT";
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ $val = array_map(
+ function($item) {
+
+ return (float)$item;
+
+ },
+ $this->getParts()
+ );
+
+ // Special-casing the GEO property.
+ //
+ // See:
+ // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
+ if ($this->name==='GEO') {
+ return array($val);
+ } else {
+ return $val;
+ }
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php b/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php
@@ -0,0 +1,61 @@
+getValue();
+ if (!strpos($input, ':')) {
+ return $input;
+ }
+ list($schema, $everythingElse) = explode(':', $input, 2);
+ return strtolower($schema) . ':' . $everythingElse;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Date.php b/vendor/sabre/vobject/lib/Property/ICalendar/Date.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/ICalendar/Date.php
@@ -0,0 +1,18 @@
+setDateTimes($parts);
+ } else {
+ parent::setParts($parts);
+ }
+
+ }
+
+ /**
+ * Updates the current value.
+ *
+ * This may be either a single, or multiple strings in an array.
+ *
+ * Instead of strings, you may also use DateTime here.
+ *
+ * @param string|array|\DateTime $value
+ * @return void
+ */
+ public function setValue($value) {
+
+ if (is_array($value) && isset($value[0]) && $value[0] instanceof \DateTime) {
+ $this->setDateTimes($value);
+ } elseif ($value instanceof \DateTime) {
+ $this->setDateTimes(array($value));
+ } else {
+ parent::setValue($value);
+ }
+
+ }
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setRawMimeDirValue($val) {
+
+ $this->setValue(explode($this->delimiter, $val));
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return implode($this->delimiter, $this->getParts());
+
+ }
+
+ /**
+ * Returns true if this is a DATE-TIME value, false if it's a DATE.
+ *
+ * @return bool
+ */
+ public function hasTime() {
+
+ return strtoupper((string)$this['VALUE']) !== 'DATE';
+
+ }
+
+ /**
+ * Returns true if this is a floating DATE or DATE-TIME.
+ *
+ * Note that DATE is always floating.
+ */
+ public function isFloating() {
+
+ return
+ !$this->hasTime() ||
+ (
+ !isset($this['TZID']) &&
+ strpos($this->getValue(),'Z')===false
+ );
+
+ }
+
+ /**
+ * Returns a date-time value.
+ *
+ * Note that if this property contained more than 1 date-time, only the
+ * first will be returned. To get an array with multiple values, call
+ * getDateTimes.
+ *
+ * If no timezone information is known, because it's either an all-day
+ * property or floating time, we will use the DateTimeZone argument to
+ * figure out the exact date.
+ *
+ * @param DateTimeZone $timeZone
+ * @return \DateTime
+ */
+ public function getDateTime(DateTimeZone $timeZone = null) {
+
+ $dt = $this->getDateTimes($timeZone);
+ if (!$dt) return null;
+
+ return $dt[0];
+
+ }
+
+ /**
+ * Returns multiple date-time values.
+ *
+ * If no timezone information is known, because it's either an all-day
+ * property or floating time, we will use the DateTimeZone argument to
+ * figure out the exact date.
+ *
+ * @param DateTimeZone $timeZone
+ * @return \DateTime[]
+ */
+ public function getDateTimes(DateTimeZone $timeZone = null) {
+
+ // Does the property have a TZID?
+ $tzid = $this['TZID'];
+
+ if ($tzid) {
+ $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root);
+ }
+
+ $dts = array();
+ foreach($this->getParts() as $part) {
+ $dts[] = DateTimeParser::parse($part, $timeZone);
+ }
+ return $dts;
+
+ }
+
+ /**
+ * Sets the property as a DateTime object.
+ *
+ * @param \DateTime $dt
+ * @param bool isFloating If set to true, timezones will be ignored.
+ * @return void
+ */
+ public function setDateTime(\DateTime $dt, $isFloating = false) {
+
+ $this->setDateTimes(array($dt), $isFloating);
+
+ }
+
+ /**
+ * Sets the property as multiple date-time objects.
+ *
+ * The first value will be used as a reference for the timezones, and all
+ * the otehr values will be adjusted for that timezone
+ *
+ * @param \DateTime[] $dt
+ * @param bool isFloating If set to true, timezones will be ignored.
+ * @return void
+ */
+ public function setDateTimes(array $dt, $isFloating = false) {
+
+ $values = array();
+
+ if($this->hasTime()) {
+
+ $tz = null;
+ $isUtc = false;
+
+ foreach($dt as $d) {
+
+ if ($isFloating) {
+ $values[] = $d->format('Ymd\\THis');
+ continue;
+ }
+ if (is_null($tz)) {
+ $tz = $d->getTimeZone();
+ $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+ if (!$isUtc) {
+ $this->offsetSet('TZID', $tz->getName());
+ }
+ } else {
+ $d->setTimeZone($tz);
+ }
+
+ if ($isUtc) {
+ $values[] = $d->format('Ymd\\THis\\Z');
+ } else {
+ $values[] = $d->format('Ymd\\THis');
+ }
+
+ }
+ if ($isUtc || $isFloating) {
+ $this->offsetUnset('TZID');
+ }
+
+ } else {
+
+ foreach($dt as $d) {
+
+ $values[] = $d->format('Ymd');
+
+ }
+ $this->offsetUnset('TZID');
+
+ }
+
+ $this->value = $values;
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return $this->hasTime()?'DATE-TIME':'DATE';
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ $dts = $this->getDateTimes();
+ $hasTime = $this->hasTime();
+ $isFloating = $this->isFloating();
+
+ $tz = $dts[0]->getTimeZone();
+ $isUtc = $isFloating ? false : in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+
+ return array_map(
+ function($dt) use ($hasTime, $isUtc) {
+
+ if ($hasTime) {
+ return $dt->format('Y-m-d\\TH:i:s') . ($isUtc?'Z':'');
+ } else {
+ return $dt->format('Y-m-d');
+ }
+
+ },
+ $dts
+ );
+
+ }
+
+ /**
+ * Sets the json value, as it would appear in a jCard or jCal object.
+ *
+ * The value must always be an array.
+ *
+ * @param array $value
+ * @return void
+ */
+ public function setJsonValue(array $value) {
+
+ // dates and times in jCal have one difference to dates and times in
+ // iCalendar. In jCal date-parts are separated by dashes, and
+ // time-parts are separated by colons. It makes sense to just remove
+ // those.
+ $this->setValue(
+ array_map(
+ function($item) {
+
+ return strtr($item, array(':'=>'', '-'=>''));
+
+ },
+ $value
+ )
+ );
+
+ }
+ /**
+ * We need to intercept offsetSet, because it may be used to alter the
+ * VALUE from DATE-TIME to DATE or vice-versa.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($name, $value) {
+
+ parent::offsetSet($name, $value);
+ if (strtoupper($name)!=='VALUE') {
+ return;
+ }
+
+ // This will ensure that dates are correctly encoded.
+ $this->setDateTimes($this->getDateTimes());
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on)
+ * 2 - An inconsequential issue
+ * 3 - A severe issue.
+ *
+ * @param int $options
+ * @return array
+ */
+ public function validate($options = 0) {
+
+ $messages = parent::validate($options);
+ $valueType = $this->getValueType();
+ $values = $this->getParts();
+ try {
+ foreach($values as $value) {
+ switch($valueType) {
+ case 'DATE' :
+ $foo = DateTimeParser::parseDate($value);
+ break;
+ case 'DATE-TIME' :
+ $foo = DateTimeParser::parseDateTime($value);
+ break;
+ }
+ }
+ } catch (\LogicException $e) {
+ $messages[] = array(
+ 'level' => 3,
+ 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType,
+ 'node' => $this,
+ );
+ }
+ return $messages;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php b/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php
@@ -0,0 +1,86 @@
+setValue(explode($this->delimiter, $val));
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return implode($this->delimiter, $this->getParts());
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return 'DURATION';
+
+ }
+
+ /**
+ * Returns a DateInterval representation of the Duration property.
+ *
+ * If the property has more than one value, only the first is returned.
+ *
+ * @return \DateInterval
+ */
+ public function getDateInterval() {
+
+ $parts = $this->getParts();
+ $value = $parts[0];
+ return DateTimeParser::parseDuration($value);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Period.php b/vendor/sabre/vobject/lib/Property/ICalendar/Period.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/ICalendar/Period.php
@@ -0,0 +1,129 @@
+setValue(explode($this->delimiter, $val));
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return implode($this->delimiter, $this->getParts());
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "PERIOD";
+
+ }
+
+ /**
+ * Sets the json value, as it would appear in a jCard or jCal object.
+ *
+ * The value must always be an array.
+ *
+ * @param array $value
+ * @return void
+ */
+ public function setJsonValue(array $value) {
+
+ $value = array_map(
+ function($item) {
+
+ return strtr(implode('/', $item), array(':' => '', '-' => ''));
+
+ },
+ $value
+ );
+ parent::setJsonValue($value);
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ $return = array();
+ foreach($this->getParts() as $item) {
+
+ list($start, $end) = explode('/', $item, 2);
+
+ $start = DateTimeParser::parseDateTime($start);
+
+ // This is a duration value.
+ if ($end[0]==='P') {
+ $return[] = array(
+ $start->format('Y-m-d\\TH:i:s'),
+ $end
+ );
+ } else {
+ $end = DateTimeParser::parseDateTime($end);
+ $return[] = array(
+ $start->format('Y-m-d\\TH:i:s'),
+ $end->format('Y-m-d\\TH:i:s'),
+ );
+ }
+
+ }
+
+ return $return;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php b/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php
@@ -0,0 +1,203 @@
+value array that is accessible using
+ * getParts, and may be set using setParts.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Recur extends Property {
+
+ /**
+ * Updates the current value.
+ *
+ * This may be either a single, or multiple strings in an array.
+ *
+ * @param string|array $value
+ * @return void
+ */
+ public function setValue($value) {
+
+ // If we're getting the data from json, we'll be receiving an object
+ if ($value instanceof \StdClass) {
+ $value = (array)$value;
+ }
+
+ if (is_array($value)) {
+ $newVal = array();
+ foreach($value as $k=>$v) {
+
+ if (is_string($v)) {
+ $v = strtoupper($v);
+
+ // The value had multiple sub-values
+ if (strpos($v,',')!==false) {
+ $v = explode(',', $v);
+ }
+ } else {
+ $v = array_map('strtoupper', $v);
+ }
+
+ $newVal[strtoupper($k)] = $v;
+ }
+ $this->value = $newVal;
+ } elseif (is_string($value)) {
+ $this->value = self::stringToArray($value);
+ } else {
+ throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
+ }
+
+ }
+
+ /**
+ * Returns the current value.
+ *
+ * This method will always return a singular value. If this was a
+ * multi-value object, some decision will be made first on how to represent
+ * it as a string.
+ *
+ * To get the correct multi-value version, use getParts.
+ *
+ * @return string
+ */
+ public function getValue() {
+
+ $out = array();
+ foreach($this->value as $key=>$value) {
+ $out[] = $key . '=' . (is_array($value)?implode(',', $value):$value);
+ }
+ return strtoupper(implode(';',$out));
+
+ }
+
+ /**
+ * Sets a multi-valued property.
+ *
+ * @param array $parts
+ * @return void
+ */
+ public function setParts(array $parts) {
+
+ $this->setValue($parts);
+
+ }
+
+ /**
+ * Returns a multi-valued property.
+ *
+ * This method always returns an array, if there was only a single value,
+ * it will still be wrapped in an array.
+ *
+ * @return array
+ */
+ public function getParts() {
+
+ return $this->value;
+
+ }
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setRawMimeDirValue($val) {
+
+ $this->setValue($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return $this->getValue();
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "RECUR";
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ $values = array();
+ foreach($this->getParts() as $k=>$v) {
+ $values[strtolower($k)] = $v;
+ }
+ return array($values);
+
+ }
+
+ /**
+ * Parses an RRULE value string, and turns it into a struct-ish array.
+ *
+ * @param string $value
+ * @return array
+ */
+ static function stringToArray($value) {
+
+ $value = strtoupper($value);
+ $newValue = array();
+ foreach(explode(';', $value) as $part) {
+
+ // Skipping empty parts.
+ if (empty($part)) {
+ continue;
+ }
+ list($partName, $partValue) = explode('=', $part);
+
+ // The value itself had multiple values..
+ if (strpos($partValue,',')!==false) {
+ $partValue=explode(',', $partValue);
+ }
+ $newValue[$partName] = $partValue;
+
+ }
+
+ return $newValue;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/IntegerValue.php b/vendor/sabre/vobject/lib/Property/IntegerValue.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/IntegerValue.php
@@ -0,0 +1,72 @@
+setValue((int)$val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return $this->value;
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "INTEGER";
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ return array((int)$this->getValue());
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/Text.php b/vendor/sabre/vobject/lib/Property/Text.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Text.php
@@ -0,0 +1,333 @@
+ 5,
+ 'ADR' => 7,
+ );
+
+ /**
+ * Creates the property.
+ *
+ * You can specify the parameters either in key=>value syntax, in which case
+ * parameters will automatically be created, or you can just pass a list of
+ * Parameter objects.
+ *
+ * @param Component $root The root document
+ * @param string $name
+ * @param string|array|null $value
+ * @param array $parameters List of parameters
+ * @param string $group The vcard property group
+ * @return void
+ */
+ public function __construct(Component $root, $name, $value = null, array $parameters = array(), $group = null) {
+
+ // There's two types of multi-valued text properties:
+ // 1. multivalue properties.
+ // 2. structured value properties
+ //
+ // The former is always separated by a comma, the latter by semi-colon.
+ if (in_array($name, $this->structuredValues)) {
+ $this->delimiter = ';';
+ }
+
+ parent::__construct($root, $name, $value, $parameters, $group);
+
+ }
+
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setRawMimeDirValue($val) {
+
+ $this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
+
+ }
+
+ /**
+ * Sets the value as a quoted-printable encoded string.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setQuotedPrintableValue($val) {
+
+ $val = quoted_printable_decode($val);
+
+ // Quoted printable only appears in vCard 2.1, and the only character
+ // that may be escaped there is ;. So we are simply splitting on just
+ // that.
+ //
+ // We also don't have to unescape \\, so all we need to look for is a ;
+ // that's not preceeded with a \.
+ $regex = '# (?setValue($matches);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ $val = $this->getParts();
+
+ if (isset($this->minimumPropertyValues[$this->name])) {
+ $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
+ }
+
+ foreach($val as &$item) {
+
+ if (!is_array($item)) {
+ $item = array($item);
+ }
+
+ foreach($item as &$subItem) {
+ $subItem = strtr(
+ $subItem,
+ array(
+ '\\' => '\\\\',
+ ';' => '\;',
+ ',' => '\,',
+ "\n" => '\n',
+ "\r" => "",
+ )
+ );
+ }
+ $item = implode(',', $item);
+
+ }
+
+ return implode($this->delimiter, $val);
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ // Structured text values should always be returned as a single
+ // array-item. Multi-value text should be returned as multiple items in
+ // the top-array.
+ if (in_array($this->name, $this->structuredValues)) {
+ return array($this->getParts());
+ } else {
+ return $this->getParts();
+ }
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "TEXT";
+
+ }
+
+ /**
+ * Turns the object back into a serialized blob.
+ *
+ * @return string
+ */
+ public function serialize() {
+
+ // We need to kick in a special type of encoding, if it's a 2.1 vcard.
+ if ($this->root->getDocumentType() !== Document::VCARD21) {
+ return parent::serialize();
+ }
+
+ $val = $this->getParts();
+
+ if (isset($this->minimumPropertyValues[$this->name])) {
+ $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
+ }
+
+ // Imploding multiple parts into a single value, and splitting the
+ // values with ;.
+ if (count($val)>1) {
+ foreach($val as $k=>$v) {
+ $val[$k] = str_replace(';','\;', $v);
+ }
+ $val = implode(';', $val);
+ } else {
+ $val = $val[0];
+ }
+
+ $str = $this->name;
+ if ($this->group) $str = $this->group . '.' . $this->name;
+ foreach($this->parameters as $param) {
+
+ if ($param->getValue() === 'QUOTED-PRINTABLE') {
+ continue;
+ }
+ $str.=';' . $param->serialize();
+
+ }
+
+
+
+ // If the resulting value contains a \n, we must encode it as
+ // quoted-printable.
+ if (strpos($val,"\n") !== false) {
+
+ $str.=';ENCODING=QUOTED-PRINTABLE:';
+ $lastLine=$str;
+ $out = null;
+
+ // The PHP built-in quoted-printable-encode does not correctly
+ // encode newlines for us. Specifically, the \r\n sequence must in
+ // vcards be encoded as =0D=OA and we must insert soft-newlines
+ // every 75 bytes.
+ for($ii=0;$ii= 32 && $ord <=126) {
+ $lastLine.=$val[$ii];
+ } else {
+ $lastLine.='=' . strtoupper(bin2hex($val[$ii]));
+ }
+ if (strlen($lastLine)>=75) {
+ // Soft line break
+ $out.=$lastLine. "=\r\n ";
+ $lastLine = null;
+ }
+
+ }
+ if (!is_null($lastLine)) $out.= $lastLine . "\r\n";
+ return $out;
+
+ } else {
+ $str.=':' . $val;
+ $out = '';
+ while(strlen($str)>0) {
+ if (strlen($str)>75) {
+ $out.= mb_strcut($str,0,75,'utf-8') . "\r\n";
+ $str = ' ' . mb_strcut($str,75,strlen($str),'utf-8');
+ } else {
+ $out.=$str . "\r\n";
+ $str='';
+ break;
+ }
+ }
+
+ return $out;
+
+
+ }
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * - Node::REPAIR - If something is broken, and automatic repair may
+ * be attempted.
+ *
+ * An array is returned with warnings.
+ *
+ * Every item in the array has the following properties:
+ * * level - (number between 1 and 3 with severity information)
+ * * message - (human readable message)
+ * * node - (reference to the offending node)
+ *
+ * @param int $options
+ * @return array
+ */
+ public function validate($options = 0) {
+
+ $warnings = parent::validate($options);
+
+ if (isset($this->minimumPropertyValues[$this->name])) {
+
+ $minimum = $this->minimumPropertyValues[$this->name];
+ $parts = $this->getParts();
+ if (count($parts) < $minimum) {
+ $warnings[] = array(
+ 'level' => 1,
+ 'message' => 'This property must have at least ' . $minimum . ' components. It only has ' . count($parts),
+ 'node' => $this,
+ );
+ if ($options & self::REPAIR) {
+ $parts = array_pad($parts, $minimum, '');
+ $this->setParts($parts);
+ }
+ }
+
+ }
+ return $warnings;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/Time.php b/vendor/sabre/vobject/lib/Property/Time.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Time.php
@@ -0,0 +1,94 @@
+getValue());
+
+ $timeStr = '';
+
+ // Hour
+ if (!is_null($parts['hour'])) {
+ $timeStr.=$parts['hour'];
+
+ if (!is_null($parts['minute'])) {
+ $timeStr.=':';
+ }
+ } else {
+ // We know either minute or second _must_ be set, so we insert a
+ // dash for an empty value.
+ $timeStr.='-';
+ }
+
+ // Minute
+ if (!is_null($parts['minute'])) {
+ $timeStr.=$parts['minute'];
+
+ if (!is_null($parts['second'])) {
+ $timeStr.=':';
+ }
+ } else {
+ if (isset($parts['second'])) {
+ // Dash for empty minute
+ $timeStr.='-';
+ }
+ }
+
+ // Second
+ if (!is_null($parts['second'])) {
+ $timeStr.=$parts['second'];
+ }
+
+ // Timezone
+ if (!is_null($parts['timezone'])) {
+ $timeStr.=$parts['timezone'];
+ }
+
+ return array($timeStr);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/Unknown.php b/vendor/sabre/vobject/lib/Property/Unknown.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Unknown.php
@@ -0,0 +1,50 @@
+getRawMimeDirValue());
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "UNKNOWN";
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/Uri.php b/vendor/sabre/vobject/lib/Property/Uri.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/Uri.php
@@ -0,0 +1,95 @@
+name === 'URL') {
+ $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x';
+ $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ $newVal = '';
+ foreach($matches as $match) {
+ switch($match) {
+ case '\:' :
+ $newVal.=':';
+ break;
+ default :
+ $newVal.=$match;
+ break;
+ }
+ }
+ $this->value = $newVal;
+ } else {
+ $this->value = $val;
+ }
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ if (is_array($this->value)) {
+ return $this->value[0];
+ } else {
+ return $this->value;
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/UtcOffset.php b/vendor/sabre/vobject/lib/Property/UtcOffset.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/UtcOffset.php
@@ -0,0 +1,37 @@
+value = $dt->format('Ymd');
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php b/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php
@@ -0,0 +1,317 @@
+1) {
+ throw new \InvalidArgumentException('Only one value allowed');
+ }
+ if (isset($parts[0]) && $parts[0] instanceof \DateTime) {
+ $this->setDateTime($parts[0]);
+ } else {
+ parent::setParts($parts);
+ }
+
+ }
+
+ /**
+ * Updates the current value.
+ *
+ * This may be either a single, or multiple strings in an array.
+ *
+ * Instead of strings, you may also use DateTime here.
+ *
+ * @param string|array|\DateTime $value
+ * @return void
+ */
+ public function setValue($value) {
+
+ if ($value instanceof \DateTime) {
+ $this->setDateTime($value);
+ } else {
+ parent::setValue($value);
+ }
+
+ }
+
+ /**
+ * Sets the property as a DateTime object.
+ *
+ * @param \DateTime $dt
+ * @return void
+ */
+ public function setDateTime(\DateTime $dt) {
+
+ $values = array();
+
+ $tz = null;
+ $isUtc = false;
+
+ $tz = $dt->getTimeZone();
+ $isUtc = in_array($tz->getName() , array('UTC', 'GMT', 'Z'));
+
+ if ($isUtc) {
+ $value = $dt->format('Ymd\\THis\\Z');
+ } else {
+ // Calculating the offset.
+ $value = $dt->format('Ymd\\THisO');
+ }
+
+ $this->value = $value;
+
+ }
+
+ /**
+ * Returns a date-time value.
+ *
+ * Note that if this property contained more than 1 date-time, only the
+ * first will be returned. To get an array with multiple values, call
+ * getDateTimes.
+ *
+ * If no time was specified, we will always use midnight (in the default
+ * timezone) as the time.
+ *
+ * If parts of the date were omitted, such as the year, we will grab the
+ * current values for those. So at the time of writing, if the year was
+ * omitted, we would have filled in 2014.
+ *
+ * @return \DateTime
+ */
+ public function getDateTime() {
+
+ $dts = array();
+ $now = new DateTime();
+
+ $tzFormat = $now->getTimezone()->getOffset($now)===0?'\\Z':'O';
+ $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat));
+
+ $value = $this->getValue();
+
+ $dateParts = DateTimeParser::parseVCardDateTime($this->getValue());
+
+ // This sets all the missing parts to the current date/time.
+ // So if the year was missing for a birthday, we're making it 'this
+ // year'.
+ foreach($dateParts as $k=>$v) {
+ if (is_null($v)) {
+ $dateParts[$k] = $nowParts[$k];
+ }
+ }
+ return new DateTime("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");
+
+ }
+
+ /**
+ * Returns the value, in the format it should be encoded for json.
+ *
+ * This method must always return an array.
+ *
+ * @return array
+ */
+ public function getJsonValue() {
+
+ $parts = DateTimeParser::parseVCardDateTime($this->getValue());
+
+ $dateStr = '';
+
+ // Year
+ if (!is_null($parts['year'])) {
+ $dateStr.=$parts['year'];
+
+ if (!is_null($parts['month'])) {
+ // If a year and a month is set, we need to insert a separator
+ // dash.
+ $dateStr.='-';
+ }
+
+ } else {
+
+ if (!is_null($parts['month']) || !is_null($parts['date'])) {
+ // Inserting two dashes
+ $dateStr.='--';
+ }
+
+ }
+
+ // Month
+
+ if (!is_null($parts['month'])) {
+ $dateStr.=$parts['month'];
+
+ if (isset($parts['date'])) {
+ // If month and date are set, we need the separator dash.
+ $dateStr.='-';
+ }
+ } else {
+ if (isset($parts['date'])) {
+ // If the month is empty, and a date is set, we need a 'empty
+ // dash'
+ $dateStr.='-';
+ }
+ }
+
+ // Date
+ if (!is_null($parts['date'])) {
+ $dateStr.=$parts['date'];
+ }
+
+
+ // Early exit if we don't have a time string.
+ if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
+ return array($dateStr);
+ }
+
+ $dateStr.='T';
+
+ // Hour
+ if (!is_null($parts['hour'])) {
+ $dateStr.=$parts['hour'];
+
+ if (!is_null($parts['minute'])) {
+ $dateStr.=':';
+ }
+ } else {
+ // We know either minute or second _must_ be set, so we insert a
+ // dash for an empty value.
+ $dateStr.='-';
+ }
+
+ // Minute
+ if (!is_null($parts['minute'])) {
+ $dateStr.=$parts['minute'];
+
+ if (!is_null($parts['second'])) {
+ $dateStr.=':';
+ }
+ } else {
+ if (isset($parts['second'])) {
+ // Dash for empty minute
+ $dateStr.='-';
+ }
+ }
+
+ // Second
+ if (!is_null($parts['second'])) {
+ $dateStr.=$parts['second'];
+ }
+
+ // Timezone
+ if (!is_null($parts['timezone'])) {
+ $dateStr.=$parts['timezone'];
+ }
+
+ return array($dateStr);
+
+ }
+
+ /**
+ * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
+ *
+ * This has been 'unfolded', so only 1 line will be passed. Unescaping is
+ * not yet done, but parameters are not included.
+ *
+ * @param string $val
+ * @return void
+ */
+ public function setRawMimeDirValue($val) {
+
+ $this->setValue($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return implode($this->delimiter, $this->getParts());
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on)
+ * 2 - An inconsequential issue
+ * 3 - A severe issue.
+ *
+ * @param int $options
+ * @return array
+ */
+ public function validate($options = 0) {
+
+ $messages = parent::validate($options);
+ $value = $this->getValue();
+ try {
+ DateTimeParser::parseVCardDateTime($value);
+ } catch (\InvalidArgumentException $e) {
+ $messages[] = array(
+ 'level' => 3,
+ 'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property',
+ 'node' => $this,
+ );
+ }
+ return $messages;
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Property/VCard/DateTime.php b/vendor/sabre/vobject/lib/Property/VCard/DateTime.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/VCard/DateTime.php
@@ -0,0 +1,33 @@
+setValue($val);
+
+ }
+
+ /**
+ * Returns a raw mime-dir representation of the value.
+ *
+ * @return string
+ */
+ public function getRawMimeDirValue() {
+
+ return $this->getValue();
+
+ }
+
+ /**
+ * Returns the type of value.
+ *
+ * This corresponds to the VALUE= parameter. Every property also has a
+ * 'default' valueType.
+ *
+ * @return string
+ */
+ public function getValueType() {
+
+ return "LANGUAGE-TAG";
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php b/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php
@@ -0,0 +1,69 @@
+getValue());
+
+ $dateStr =
+ $parts['year'] . '-' .
+ $parts['month'] . '-' .
+ $parts['date'] . 'T' .
+ $parts['hour'] . ':' .
+ $parts['minute'] . ':' .
+ $parts['second'];
+
+ // Timezone
+ if (!is_null($parts['timezone'])) {
+ $dateStr.=$parts['timezone'];
+ }
+
+ return array($dateStr);
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Reader.php b/vendor/sabre/vobject/lib/Reader.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Reader.php
@@ -0,0 +1,73 @@
+parse($data, $options);
+
+ return $result;
+
+ }
+
+ /**
+ * Parses a jCard or jCal object, and returns the top component.
+ *
+ * The options argument is a bitfield. Pass any of the OPTIONS constant to
+ * alter the parsers' behaviour.
+ *
+ * You can either a string, a readable stream, or an array for it's input.
+ * Specifying the array is useful if json_decode was already called on the
+ * input.
+ *
+ * @param string|resource|array $data
+ * @param int $options
+ * @return Node
+ */
+ static public function readJson($data, $options = 0) {
+
+ $parser = new Parser\Json();
+ $result = $parser->parse($data, $options);
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Recur/EventIterator.php b/vendor/sabre/vobject/lib/Recur/EventIterator.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Recur/EventIterator.php
@@ -0,0 +1,496 @@
+timeZone)) {
+ $timeZone = new DateTimeZone('UTC');
+ }
+ $this->timeZone = $timeZone;
+
+ if (is_array($input)) {
+ $events = $input;
+ } elseif ($input instanceof VEvent) {
+ // Single instance mode.
+ $events = array($input);
+ } else {
+ // Calendar + UID mode.
+ $uid = (string)$uid;
+ if (!$uid) {
+ throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
+ }
+ if (!isset($input->VEVENT)) {
+ throw new InvalidArgumentException('No events found in this calendar');
+ }
+ $events = $input->getByUID($uid);
+
+ }
+
+ foreach($events as $vevent) {
+
+ if (!isset($vevent->{'RECURRENCE-ID'})) {
+
+ $this->masterEvent = $vevent;
+
+ } else {
+
+ $this->exceptions[
+ $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
+ ] = true;
+ $this->overriddenEvents[] = $vevent;
+
+ }
+
+ }
+
+ if (!$this->masterEvent) {
+ // No base event was found. CalDAV does allow cases where only
+ // overridden instances are stored.
+ //
+ // In this particular case, we're just going to grab the first
+ // event and use that instead. This may not always give the
+ // desired result.
+ if (!count($this->overriddenEvents)) {
+ throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
+ }
+ $this->masterEvent = array_shift($this->overriddenEvents);
+ }
+
+ $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
+ $this->allDay = !$this->masterEvent->DTSTART->hasTime();
+
+ if (isset($this->masterEvent->EXDATE)) {
+
+ foreach($this->masterEvent->EXDATE as $exDate) {
+
+ foreach($exDate->getDateTimes($this->timeZone) as $dt) {
+ $this->exceptions[$dt->getTimeStamp()] = true;
+ }
+
+ }
+
+ }
+
+ if (isset($this->masterEvent->DTEND)) {
+ $this->eventDuration =
+ $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
+ $this->startDate->getTimeStamp();
+ } elseif (isset($this->masterEvent->DURATION)) {
+ $duration = $this->masterEvent->DURATION->getDateInterval();
+ $end = clone $this->startDate;
+ $end->add($duration);
+ $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
+ } elseif ($this->allDay) {
+ $this->eventDuration = 3600 * 24;
+ } else {
+ $this->eventDuration = 0;
+ }
+
+ if (isset($this->masterEvent->RDATE)) {
+ $this->recurIterator = new RDateIterator(
+ $this->masterEvent->RDATE->getParts(),
+ $this->startDate
+ );
+ } elseif (isset($this->masterEvent->RRULE)) {
+ $this->recurIterator = new RRuleIterator(
+ $this->masterEvent->RRULE->getParts(),
+ $this->startDate
+ );
+ } else {
+ $this->recurIterator = new RRuleIterator(
+ array(
+ 'FREQ' => 'DAILY',
+ 'COUNT' => 1,
+ ),
+ $this->startDate
+ );
+ }
+
+ $this->rewind();
+ if (!$this->valid()) {
+ throw new NoInstancesException('This recurrence rule does not generate any valid instances');
+ }
+
+ }
+
+ /**
+ * Returns the date for the current position of the iterator.
+ *
+ * @return DateTime
+ */
+ public function current() {
+
+ if ($this->currentDate) {
+ return clone $this->currentDate;
+ }
+
+ }
+
+ /**
+ * This method returns the start date for the current iteration of the
+ * event.
+ *
+ * @return DateTime
+ */
+ public function getDtStart() {
+
+ if ($this->currentDate) {
+ return clone $this->currentDate;
+ }
+
+ }
+
+ /**
+ * This method returns the end date for the current iteration of the
+ * event.
+ *
+ * @return DateTime
+ */
+ public function getDtEnd() {
+
+ if (!$this->valid()) {
+ return null;
+ }
+ $end = clone $this->currentDate;
+ $end->modify('+' . $this->eventDuration . ' seconds');
+ return $end;
+
+ }
+
+ /**
+ * Returns a VEVENT for the current iterations of the event.
+ *
+ * This VEVENT will have a recurrence id, and it's DTSTART and DTEND
+ * altered.
+ *
+ * @return VEvent
+ */
+ public function getEventObject() {
+
+ if ($this->currentOverriddenEvent) {
+ return $this->currentOverriddenEvent;
+ }
+
+ $event = clone $this->masterEvent;
+
+ // Ignoring the following block, because PHPUnit's code coverage
+ // ignores most of these lines, and this messes with our stats.
+ //
+ // @codeCoverageIgnoreStart
+ unset(
+ $event->RRULE,
+ $event->EXDATE,
+ $event->RDATE,
+ $event->EXRULE,
+ $event->{'RECURRENCE-ID'}
+ );
+ // @codeCoverageIgnoreEnd
+
+ $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
+ if (isset($event->DTEND)) {
+ $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
+ }
+ $recurid = clone $event->DTSTART;
+ $recurid->name = 'RECURRENCE-ID';
+ $event->add($recurid);
+ return $event;
+
+ }
+
+ /**
+ * Returns the current position of the iterator.
+ *
+ * This is for us simply a 0-based index.
+ *
+ * @return int
+ */
+ public function key() {
+
+ // The counter is always 1 ahead.
+ return $this->counter - 1;
+
+ }
+
+ /**
+ * This is called after next, to see if the iterator is still at a valid
+ * position, or if it's at the end.
+ *
+ * @return bool
+ */
+ public function valid() {
+
+ return !!$this->currentDate;
+
+ }
+
+ /**
+ * Sets the iterator back to the starting point.
+ */
+ public function rewind() {
+
+ $this->recurIterator->rewind();
+ // re-creating overridden event index.
+ $index = array();
+ foreach($this->overriddenEvents as $key=>$event) {
+ $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
+ $index[$stamp] = $key;
+ }
+ krsort($index);
+ $this->counter = 0;
+ $this->overriddenEventsIndex = $index;
+ $this->currentOverriddenEvent = null;
+
+ $this->nextDate = null;
+ $this->currentDate = clone $this->startDate;
+
+ $this->next();
+
+ }
+
+ /**
+ * Advances the iterator with one step.
+ *
+ * @return void
+ */
+ public function next() {
+
+ $this->currentOverriddenEvent = null;
+ $this->counter++;
+ if ($this->nextDate) {
+ // We had a stored value.
+ $nextDate = $this->nextDate;
+ $this->nextDate = null;
+ } else {
+ // We need to ask rruleparser for the next date.
+ // We need to do this until we find a date that's not in the
+ // exception list.
+ do {
+ if (!$this->recurIterator->valid()) {
+ $nextDate = null;
+ break;
+ }
+ $nextDate = $this->recurIterator->current();
+ $this->recurIterator->next();
+ } while(isset($this->exceptions[$nextDate->getTimeStamp()]));
+
+ }
+
+
+ // $nextDate now contains what rrule thinks is the next one, but an
+ // overridden event may cut ahead.
+ if ($this->overriddenEventsIndex) {
+
+ $offset = end($this->overriddenEventsIndex);
+ $timestamp = key($this->overriddenEventsIndex);
+ if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
+ // Overridden event comes first.
+ $this->currentOverriddenEvent = $this->overriddenEvents[$offset];
+
+ // Putting the rrule next date aside.
+ $this->nextDate = $nextDate;
+ $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
+
+ // Ensuring that this item will only be used once.
+ array_pop($this->overriddenEventsIndex);
+
+ // Exit point!
+ return;
+
+ }
+
+ }
+
+ $this->currentDate = $nextDate;
+
+ }
+
+ /**
+ * Quickly jump to a date in the future.
+ *
+ * @param DateTime $dateTime
+ */
+ public function fastForward(DateTime $dateTime) {
+
+ while($this->valid() && $this->getDtEnd() < $dateTime ) {
+ $this->next();
+ }
+
+ }
+
+ /**
+ * Returns true if this recurring event never ends.
+ *
+ * @return bool
+ */
+ public function isInfinite() {
+
+ return $this->recurIterator->isInfinite();
+
+ }
+
+ /**
+ * RRULE parser
+ *
+ * @var RRuleIterator
+ */
+ protected $recurIterator;
+
+ /**
+ * The duration, in seconds, of the master event.
+ *
+ * We use this to calculate the DTEND for subsequent events.
+ */
+ protected $eventDuration;
+
+ /**
+ * A reference to the main (master) event.
+ *
+ * @var VEVENT
+ */
+ protected $masterEvent;
+
+ /**
+ * List of overridden events.
+ *
+ * @var array
+ */
+ protected $overriddenEvents = array();
+
+ /**
+ * Overridden event index.
+ *
+ * Key is timestamp, value is the index of the item in the $overriddenEvent
+ * property.
+ *
+ * @var array
+ */
+ protected $overriddenEventsIndex;
+
+ /**
+ * A list of recurrence-id's that are either part of EXDATE, or are
+ * overridden.
+ *
+ * @var array
+ */
+ protected $exceptions = array();
+
+ /**
+ * Internal event counter
+ *
+ * @var int
+ */
+ protected $counter;
+
+ /**
+ * The very start of the iteration process.
+ *
+ * @var DateTime
+ */
+ protected $startDate;
+
+ /**
+ * Where we are currently in the iteration process
+ *
+ * @var DateTime
+ */
+ protected $currentDate;
+
+ /**
+ * The next date from the rrule parser.
+ *
+ * Sometimes we need to temporary store the next date, because an
+ * overridden event came before.
+ *
+ * @var DateTime
+ */
+ protected $nextDate;
+
+}
diff --git a/vendor/sabre/vobject/lib/Recur/NoInstancesException.php b/vendor/sabre/vobject/lib/Recur/NoInstancesException.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Recur/NoInstancesException.php
@@ -0,0 +1,18 @@
+startDate = $start;
+ $this->parseRDate($rrule);
+ $this->currentDate = clone $this->startDate;
+
+ }
+
+ /* Implementation of the Iterator interface {{{ */
+
+ public function current() {
+
+ if (!$this->valid()) return null;
+ return clone $this->currentDate;
+
+ }
+
+ /**
+ * Returns the current item number.
+ *
+ * @return int
+ */
+ public function key() {
+
+ return $this->counter;
+
+ }
+
+ /**
+ * Returns whether the current item is a valid item for the recurrence
+ * iterator.
+ *
+ * @return bool
+ */
+ public function valid() {
+
+ return ($this->counter <= count($this->dates));
+
+ }
+
+ /**
+ * Resets the iterator.
+ *
+ * @return void
+ */
+ public function rewind() {
+
+ $this->currentDate = clone $this->startDate;
+ $this->counter = 0;
+
+ }
+
+ /**
+ * Goes on to the next iteration.
+ *
+ * @return void
+ */
+ public function next() {
+
+ $this->counter++;
+ if (!$this->valid()) return;
+
+ $this->currentDate =
+ DateTimeParser::parse(
+ $this->dates[$this->counter-1]
+ );
+
+ }
+
+ /* End of Iterator implementation }}} */
+
+ /**
+ * Returns true if this recurring event never ends.
+ *
+ * @return bool
+ */
+ public function isInfinite() {
+
+ return false;
+
+ }
+
+ /**
+ * This method allows you to quickly go to the next occurrence after the
+ * specified date.
+ *
+ * @param DateTime $dt
+ * @return void
+ */
+ public function fastForward(\DateTime $dt) {
+
+ while($this->valid() && $this->currentDate < $dt ) {
+ $this->next();
+ }
+
+ }
+
+ /**
+ * The reference start date/time for the rrule.
+ *
+ * All calculations are based on this initial date.
+ *
+ * @var DateTime
+ */
+ protected $startDate;
+
+ /**
+ * The date of the current iteration. You can get this by calling
+ * ->current().
+ *
+ * @var DateTime
+ */
+ protected $currentDate;
+
+ /**
+ * The current item in the list.
+ *
+ * You can get this number with the key() method.
+ *
+ * @var int
+ */
+ protected $counter = 0;
+
+ /* }}} */
+
+ /**
+ * This method receives a string from an RRULE property, and populates this
+ * class with all the values.
+ *
+ * @param string|array $rrule
+ * @return void
+ */
+ protected function parseRDate($rdate) {
+
+ if (is_string($rdate)) {
+ $rdate = explode(',', $rdate);
+ }
+
+ $this->dates = $rdate;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Recur/RRuleIterator.php b/vendor/sabre/vobject/lib/Recur/RRuleIterator.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Recur/RRuleIterator.php
@@ -0,0 +1,904 @@
+startDate = $start;
+ $this->parseRRule($rrule);
+ $this->currentDate = clone $this->startDate;
+
+ }
+
+ /* Implementation of the Iterator interface {{{ */
+
+ public function current() {
+
+ if (!$this->valid()) return null;
+ return clone $this->currentDate;
+
+ }
+
+ /**
+ * Returns the current item number
+ *
+ * @return int
+ */
+ public function key() {
+
+ return $this->counter;
+
+ }
+
+ /**
+ * Returns whether the current item is a valid item for the recurrence
+ * iterator. This will return false if we've gone beyond the UNTIL or COUNT
+ * statements.
+ *
+ * @return bool
+ */
+ public function valid() {
+
+ if (!is_null($this->count)) {
+ return $this->counter < $this->count;
+ }
+ return is_null($this->until) || $this->currentDate <= $this->until;
+
+ }
+
+ /**
+ * Resets the iterator
+ *
+ * @return void
+ */
+ public function rewind() {
+
+ $this->currentDate = clone $this->startDate;
+ $this->counter = 0;
+
+ }
+
+ /**
+ * Goes on to the next iteration
+ *
+ * @return void
+ */
+ public function next() {
+
+ $previousStamp = $this->currentDate->getTimeStamp();
+
+ // Otherwise, we find the next event in the normal RRULE
+ // sequence.
+ switch($this->frequency) {
+
+ case 'hourly' :
+ $this->nextHourly();
+ break;
+
+ case 'daily' :
+ $this->nextDaily();
+ break;
+
+ case 'weekly' :
+ $this->nextWeekly();
+ break;
+
+ case 'monthly' :
+ $this->nextMonthly();
+ break;
+
+ case 'yearly' :
+ $this->nextYearly();
+ break;
+
+ }
+ $this->counter++;
+
+ }
+
+ /* End of Iterator implementation }}} */
+
+ /**
+ * Returns true if this recurring event never ends.
+ *
+ * @return bool
+ */
+ public function isInfinite() {
+
+ return !$this->count && !$this->until;
+
+ }
+
+ /**
+ * This method allows you to quickly go to the next occurrence after the
+ * specified date.
+ *
+ * @param DateTime $dt
+ * @return void
+ */
+ public function fastForward(\DateTime $dt) {
+
+ while($this->valid() && $this->currentDate < $dt ) {
+ $this->next();
+ }
+
+ }
+
+ /**
+ * The reference start date/time for the rrule.
+ *
+ * All calculations are based on this initial date.
+ *
+ * @var DateTime
+ */
+ protected $startDate;
+
+ /**
+ * The date of the current iteration. You can get this by calling
+ * ->current().
+ *
+ * @var DateTime
+ */
+ protected $currentDate;
+
+ /**
+ * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
+ * yearly.
+ *
+ * @var string
+ */
+ protected $frequency;
+
+ /**
+ * The number of recurrences, or 'null' if infinitely recurring.
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * The interval.
+ *
+ * If for example frequency is set to daily, interval = 2 would mean every
+ * 2 days.
+ *
+ * @var int
+ */
+ protected $interval = 1;
+
+ /**
+ * The last instance of this recurrence, inclusively
+ *
+ * @var \DateTime|null
+ */
+ protected $until;
+
+ /**
+ * Which seconds to recur.
+ *
+ * This is an array of integers (between 0 and 60)
+ *
+ * @var array
+ */
+ protected $bySecond;
+
+ /**
+ * Which minutes to recur
+ *
+ * This is an array of integers (between 0 and 59)
+ *
+ * @var array
+ */
+ protected $byMinute;
+
+ /**
+ * Which hours to recur
+ *
+ * This is an array of integers (between 0 and 23)
+ *
+ * @var array
+ */
+ protected $byHour;
+
+ /**
+ * The current item in the list.
+ *
+ * You can get this number with the key() method.
+ *
+ * @var int
+ */
+ protected $counter = 0;
+
+ /**
+ * Which weekdays to recur.
+ *
+ * This is an array of weekdays
+ *
+ * This may also be preceeded by a positive or negative integer. If present,
+ * this indicates the nth occurrence of a specific day within the monthly or
+ * yearly rrule. For instance, -2TU indicates the second-last tuesday of
+ * the month, or year.
+ *
+ * @var array
+ */
+ protected $byDay;
+
+ /**
+ * Which days of the month to recur
+ *
+ * This is an array of days of the months (1-31). The value can also be
+ * negative. -5 for instance means the 5th last day of the month.
+ *
+ * @var array
+ */
+ protected $byMonthDay;
+
+ /**
+ * Which days of the year to recur.
+ *
+ * This is an array with days of the year (1 to 366). The values can also
+ * be negative. For instance, -1 will always represent the last day of the
+ * year. (December 31st).
+ *
+ * @var array
+ */
+ protected $byYearDay;
+
+ /**
+ * Which week numbers to recur.
+ *
+ * This is an array of integers from 1 to 53. The values can also be
+ * negative. -1 will always refer to the last week of the year.
+ *
+ * @var array
+ */
+ protected $byWeekNo;
+
+ /**
+ * Which months to recur.
+ *
+ * This is an array of integers from 1 to 12.
+ *
+ * @var array
+ */
+ protected $byMonth;
+
+ /**
+ * Which items in an existing st to recur.
+ *
+ * These numbers work together with an existing by* rule. It specifies
+ * exactly which items of the existing by-rule to filter.
+ *
+ * Valid values are 1 to 366 and -1 to -366. As an example, this can be
+ * used to recur the last workday of the month.
+ *
+ * This would be done by setting frequency to 'monthly', byDay to
+ * 'MO,TU,WE,TH,FR' and bySetPos to -1.
+ *
+ * @var array
+ */
+ protected $bySetPos;
+
+ /**
+ * When the week starts.
+ *
+ * @var string
+ */
+ protected $weekStart = 'MO';
+
+ /* Functions that advance the iterator {{{ */
+
+ /**
+ * Does the processing for advancing the iterator for hourly frequency.
+ *
+ * @return void
+ */
+ protected function nextHourly() {
+
+ $this->currentDate->modify('+' . $this->interval . ' hours');
+
+ }
+
+ /**
+ * Does the processing for advancing the iterator for daily frequency.
+ *
+ * @return void
+ */
+ protected function nextDaily() {
+
+ if (!$this->byHour && !$this->byDay) {
+ $this->currentDate->modify('+' . $this->interval . ' days');
+ return;
+ }
+
+ if (isset($this->byHour)) {
+ $recurrenceHours = $this->getHours();
+ }
+
+ if (isset($this->byDay)) {
+ $recurrenceDays = $this->getDays();
+ }
+
+ if (isset($this->byMonth)) {
+ $recurrenceMonths = $this->getMonths();
+ }
+
+ do {
+ if ($this->byHour) {
+ if ($this->currentDate->format('G') == '23') {
+ // to obey the interval rule
+ $this->currentDate->modify('+' . $this->interval-1 . ' days');
+ }
+
+ $this->currentDate->modify('+1 hours');
+
+ } else {
+ $this->currentDate->modify('+' . $this->interval . ' days');
+
+ }
+
+ // Current month of the year
+ $currentMonth = $this->currentDate->format('n');
+
+ // Current day of the week
+ $currentDay = $this->currentDate->format('w');
+
+ // Current hour of the day
+ $currentHour = $this->currentDate->format('G');
+
+ } while (
+ ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
+ ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
+ ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
+ );
+
+ }
+
+ /**
+ * Does the processing for advancing the iterator for weekly frequency.
+ *
+ * @return void
+ */
+ protected function nextWeekly() {
+
+ if (!$this->byHour && !$this->byDay) {
+ $this->currentDate->modify('+' . $this->interval . ' weeks');
+ return;
+ }
+
+ if ($this->byHour) {
+ $recurrenceHours = $this->getHours();
+ }
+
+ if ($this->byDay) {
+ $recurrenceDays = $this->getDays();
+ }
+
+ // First day of the week:
+ $firstDay = $this->dayMap[$this->weekStart];
+
+ do {
+
+ if ($this->byHour) {
+ $this->currentDate->modify('+1 hours');
+ } else {
+ $this->currentDate->modify('+1 days');
+ }
+
+ // Current day of the week
+ $currentDay = (int) $this->currentDate->format('w');
+
+ // Current hour of the day
+ $currentHour = (int) $this->currentDate->format('G');
+
+ // We need to roll over to the next week
+ if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
+ $this->currentDate->modify('+' . $this->interval-1 . ' weeks');
+
+ // We need to go to the first day of this week, but only if we
+ // are not already on this first day of this week.
+ if($this->currentDate->format('w') != $firstDay) {
+ $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
+ }
+ }
+
+ // We have a match
+ } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
+ }
+
+ /**
+ * Does the processing for advancing the iterator for monthly frequency.
+ *
+ * @return void
+ */
+ protected function nextMonthly() {
+
+ $currentDayOfMonth = $this->currentDate->format('j');
+ if (!$this->byMonthDay && !$this->byDay) {
+
+ // If the current day is higher than the 28th, rollover can
+ // occur to the next month. We Must skip these invalid
+ // entries.
+ if ($currentDayOfMonth < 29) {
+ $this->currentDate->modify('+' . $this->interval . ' months');
+ } else {
+ $increase = 0;
+ do {
+ $increase++;
+ $tempDate = clone $this->currentDate;
+ $tempDate->modify('+ ' . ($this->interval*$increase) . ' months');
+ } while ($tempDate->format('j') != $currentDayOfMonth);
+ $this->currentDate = $tempDate;
+ }
+ return;
+ }
+
+ while(true) {
+
+ $occurrences = $this->getMonthlyOccurrences();
+
+ foreach($occurrences as $occurrence) {
+
+ // The first occurrence thats higher than the current
+ // day of the month wins.
+ if ($occurrence > $currentDayOfMonth) {
+ break 2;
+ }
+
+ }
+
+ // If we made it all the way here, it means there were no
+ // valid occurrences, and we need to advance to the next
+ // month.
+ //
+ // This line does not currently work in hhvm. Temporary workaround
+ // follows:
+ // $this->currentDate->modify('first day of this month');
+ $this->currentDate = new \DateTime($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
+ // end of workaround
+ $this->currentDate->modify('+ ' . $this->interval . ' months');
+
+ // This goes to 0 because we need to start counting at the
+ // beginning.
+ $currentDayOfMonth = 0;
+
+ }
+
+ $this->currentDate->setDate($this->currentDate->format('Y'), $this->currentDate->format('n'), $occurrence);
+
+ }
+
+ /**
+ * Does the processing for advancing the iterator for yearly frequency.
+ *
+ * @return void
+ */
+ protected function nextYearly() {
+
+ $currentMonth = $this->currentDate->format('n');
+ $currentYear = $this->currentDate->format('Y');
+ $currentDayOfMonth = $this->currentDate->format('j');
+
+ // No sub-rules, so we just advance by year
+ if (!$this->byMonth) {
+
+ // Unless it was a leap day!
+ if ($currentMonth==2 && $currentDayOfMonth==29) {
+
+ $counter = 0;
+ do {
+ $counter++;
+ // Here we increase the year count by the interval, until
+ // we hit a date that's also in a leap year.
+ //
+ // We could just find the next interval that's dividable by
+ // 4, but that would ignore the rule that there's no leap
+ // year every year that's dividable by a 100, but not by
+ // 400. (1800, 1900, 2100). So we just rely on the datetime
+ // functions instead.
+ $nextDate = clone $this->currentDate;
+ $nextDate->modify('+ ' . ($this->interval*$counter) . ' years');
+ } while ($nextDate->format('n')!=2);
+ $this->currentDate = $nextDate;
+
+ return;
+
+ }
+
+ // The easiest form
+ $this->currentDate->modify('+' . $this->interval . ' years');
+ return;
+
+ }
+
+ $currentMonth = $this->currentDate->format('n');
+ $currentYear = $this->currentDate->format('Y');
+ $currentDayOfMonth = $this->currentDate->format('j');
+
+ $advancedToNewMonth = false;
+
+ // If we got a byDay or getMonthDay filter, we must first expand
+ // further.
+ if ($this->byDay || $this->byMonthDay) {
+
+ while(true) {
+
+ $occurrences = $this->getMonthlyOccurrences();
+
+ foreach($occurrences as $occurrence) {
+
+ // The first occurrence that's higher than the current
+ // day of the month wins.
+ // If we advanced to the next month or year, the first
+ // occurrence is always correct.
+ if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
+ break 2;
+ }
+
+ }
+
+ // If we made it here, it means we need to advance to
+ // the next month or year.
+ $currentDayOfMonth = 1;
+ $advancedToNewMonth = true;
+ do {
+
+ $currentMonth++;
+ if ($currentMonth>12) {
+ $currentYear+=$this->interval;
+ $currentMonth = 1;
+ }
+ } while (!in_array($currentMonth, $this->byMonth));
+
+ $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+ }
+
+ // If we made it here, it means we got a valid occurrence
+ $this->currentDate->setDate($currentYear, $currentMonth, $occurrence);
+ return;
+
+ } else {
+
+ // These are the 'byMonth' rules, if there are no byDay or
+ // byMonthDay sub-rules.
+ do {
+
+ $currentMonth++;
+ if ($currentMonth>12) {
+ $currentYear+=$this->interval;
+ $currentMonth = 1;
+ }
+ } while (!in_array($currentMonth, $this->byMonth));
+ $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth);
+
+ return;
+
+ }
+
+ }
+
+ /* }}} */
+
+ /**
+ * This method receives a string from an RRULE property, and populates this
+ * class with all the values.
+ *
+ * @param string|array $rrule
+ * @return void
+ */
+ protected function parseRRule($rrule) {
+
+ if (is_string($rrule)) {
+ $rrule = Property\ICalendar\Recur::stringToArray($rrule);
+ }
+
+ foreach($rrule as $key=>$value) {
+
+ $key = strtoupper($key);
+ switch($key) {
+
+ case 'FREQ' :
+ $value = strtolower($value);
+ if (!in_array(
+ $value,
+ array('secondly','minutely','hourly','daily','weekly','monthly','yearly')
+ )) {
+ throw new InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value));
+ }
+ $this->frequency = $value;
+ break;
+
+ case 'UNTIL' :
+ $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
+
+ // In some cases events are generated with an UNTIL=
+ // parameter before the actual start of the event.
+ //
+ // Not sure why this is happening. We assume that the
+ // intention was that the event only recurs once.
+ //
+ // So we are modifying the parameter so our code doesn't
+ // break.
+ if($this->until < $this->startDate) {
+ $this->until = $this->startDate;
+ }
+ break;
+
+ case 'INTERVAL' :
+ // No break
+
+ case 'COUNT' :
+ $val = (int)$value;
+ if ($val < 1) {
+ throw new \InvalidArgumentException(strtoupper($key) . ' in RRULE must be a positive integer!');
+ }
+ $key = strtolower($key);
+ $this->$key = $val;
+ break;
+
+ case 'BYSECOND' :
+ $this->bySecond = (array)$value;
+ break;
+
+ case 'BYMINUTE' :
+ $this->byMinute = (array)$value;
+ break;
+
+ case 'BYHOUR' :
+ $this->byHour = (array)$value;
+ break;
+
+ case 'BYDAY' :
+ $value = (array)$value;
+ foreach($value as $part) {
+ if (!preg_match('#^ (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
+ throw new \InvalidArgumentException('Invalid part in BYDAY clause: ' . $part);
+ }
+ }
+ $this->byDay = $value;
+ break;
+
+ case 'BYMONTHDAY' :
+ $this->byMonthDay = (array)$value;
+ break;
+
+ case 'BYYEARDAY' :
+ $this->byYearDay = (array)$value;
+ break;
+
+ case 'BYWEEKNO' :
+ $this->byWeekNo = (array)$value;
+ break;
+
+ case 'BYMONTH' :
+ $this->byMonth = (array)$value;
+ break;
+
+ case 'BYSETPOS' :
+ $this->bySetPos = (array)$value;
+ break;
+
+ case 'WKST' :
+ $this->weekStart = strtoupper($value);
+ break;
+
+ default:
+ throw new \InvalidArgumentException('Not supported: ' . strtoupper($key));
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Mappings between the day number and english day name.
+ *
+ * @var array
+ */
+ protected $dayNames = array(
+ 0 => 'Sunday',
+ 1 => 'Monday',
+ 2 => 'Tuesday',
+ 3 => 'Wednesday',
+ 4 => 'Thursday',
+ 5 => 'Friday',
+ 6 => 'Saturday',
+ );
+
+ /**
+ * Returns all the occurrences for a monthly frequency with a 'byDay' or
+ * 'byMonthDay' expansion for the current month.
+ *
+ * The returned list is an array of integers with the day of month (1-31).
+ *
+ * @return array
+ */
+ protected function getMonthlyOccurrences() {
+
+ $startDate = clone $this->currentDate;
+
+ $byDayResults = array();
+
+ // Our strategy is to simply go through the byDays, advance the date to
+ // that point and add it to the results.
+ if ($this->byDay) foreach($this->byDay as $day) {
+
+ $dayName = $this->dayNames[$this->dayMap[substr($day,-2)]];
+
+
+ // Dayname will be something like 'wednesday'. Now we need to find
+ // all wednesdays in this month.
+ $dayHits = array();
+
+ // workaround for missing 'first day of the month' support in hhvm
+ $checkDate = new \DateTime($startDate->format('Y-m-1'));
+ // workaround modify always advancing the date even if the current day is a $dayName in hhvm
+ if ($checkDate->format('l') !== $dayName) {
+ $checkDate->modify($dayName);
+ }
+
+ do {
+ $dayHits[] = $checkDate->format('j');
+ $checkDate->modify('next ' . $dayName);
+ } while ($checkDate->format('n') === $startDate->format('n'));
+
+ // So now we have 'all wednesdays' for month. It is however
+ // possible that the user only really wanted the 1st, 2nd or last
+ // wednesday.
+ if (strlen($day)>2) {
+ $offset = (int)substr($day,0,-2);
+
+ if ($offset>0) {
+ // It is possible that the day does not exist, such as a
+ // 5th or 6th wednesday of the month.
+ if (isset($dayHits[$offset-1])) {
+ $byDayResults[] = $dayHits[$offset-1];
+ }
+ } else {
+
+ // if it was negative we count from the end of the array
+ // might not exist, fx. -5th tuesday
+ if (isset($dayHits[count($dayHits) + $offset])) {
+ $byDayResults[] = $dayHits[count($dayHits) + $offset];
+ }
+ }
+ } else {
+ // There was no counter (first, second, last wednesdays), so we
+ // just need to add the all to the list).
+ $byDayResults = array_merge($byDayResults, $dayHits);
+
+ }
+
+ }
+
+ $byMonthDayResults = array();
+ if ($this->byMonthDay) foreach($this->byMonthDay as $monthDay) {
+
+ // Removing values that are out of range for this month
+ if ($monthDay > $startDate->format('t') ||
+ $monthDay < 0-$startDate->format('t')) {
+ continue;
+ }
+ if ($monthDay>0) {
+ $byMonthDayResults[] = $monthDay;
+ } else {
+ // Negative values
+ $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
+ }
+ }
+
+ // If there was just byDay or just byMonthDay, they just specify our
+ // (almost) final list. If both were provided, then byDay limits the
+ // list.
+ if ($this->byMonthDay && $this->byDay) {
+ $result = array_intersect($byMonthDayResults, $byDayResults);
+ } elseif ($this->byMonthDay) {
+ $result = $byMonthDayResults;
+ } else {
+ $result = $byDayResults;
+ }
+ $result = array_unique($result);
+ sort($result, SORT_NUMERIC);
+
+ // The last thing that needs checking is the BYSETPOS. If it's set, it
+ // means only certain items in the set survive the filter.
+ if (!$this->bySetPos) {
+ return $result;
+ }
+
+ $filteredResult = array();
+ foreach($this->bySetPos as $setPos) {
+
+ if ($setPos<0) {
+ $setPos = count($result)+($setPos+1);
+ }
+ if (isset($result[$setPos-1])) {
+ $filteredResult[] = $result[$setPos-1];
+ }
+ }
+
+ sort($filteredResult, SORT_NUMERIC);
+ return $filteredResult;
+
+ }
+
+ /**
+ * Simple mapping from iCalendar day names to day numbers
+ *
+ * @var array
+ */
+ protected $dayMap = array(
+ 'SU' => 0,
+ 'MO' => 1,
+ 'TU' => 2,
+ 'WE' => 3,
+ 'TH' => 4,
+ 'FR' => 5,
+ 'SA' => 6,
+ );
+
+ protected function getHours()
+ {
+ $recurrenceHours = array();
+ foreach($this->byHour as $byHour) {
+ $recurrenceHours[] = $byHour;
+ }
+
+ return $recurrenceHours;
+ }
+
+ protected function getDays() {
+
+ $recurrenceDays = array();
+ foreach($this->byDay as $byDay) {
+
+ // The day may be preceeded with a positive (+n) or
+ // negative (-n) integer. However, this does not make
+ // sense in 'weekly' so we ignore it here.
+ $recurrenceDays[] = $this->dayMap[substr($byDay,-2)];
+
+ }
+
+ return $recurrenceDays;
+ }
+
+ protected function getMonths() {
+
+ $recurrenceMonths = array();
+ foreach($this->byMonth as $byMonth) {
+ $recurrenceMonths[] = $byMonth;
+ }
+
+ return $recurrenceMonths;
+ }
+}
diff --git a/vendor/sabre/vobject/lib/RecurrenceIterator.php b/vendor/sabre/vobject/lib/RecurrenceIterator.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/RecurrenceIterator.php
@@ -0,0 +1,21 @@
+children() as $component) {
+ if (!$component instanceof VObject\Component) {
+ continue;
+ }
+
+ // Get all timezones
+ if ($component->name === 'VTIMEZONE') {
+ $this->vtimezones[(string)$component->TZID] = $component;
+ continue;
+ }
+
+ // Get component UID for recurring Events search
+ if(!$component->UID) {
+ $component->UID = sha1(microtime()) . '-vobjectimport';
+ }
+ $uid = (string)$component->UID;
+
+ // Take care of recurring events
+ if (!array_key_exists($uid, $this->objects)) {
+ $this->objects[$uid] = new VCalendar();
+ }
+
+ $this->objects[$uid]->add(clone $component);
+ }
+
+ }
+
+ /**
+ * Every time getNext() is called, a new object will be parsed, until we
+ * hit the end of the stream.
+ *
+ * When the end is reached, null will be returned.
+ *
+ * @return Sabre\VObject\Component|null
+ */
+ public function getNext() {
+
+ if($object=array_shift($this->objects)) {
+
+ // create our baseobject
+ $object->version = '2.0';
+ $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
+ $object->calscale = 'GREGORIAN';
+
+ // add vtimezone information to obj (if we have it)
+ foreach ($this->vtimezones as $vtimezone) {
+ $object->add($vtimezone);
+ }
+
+ return $object;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php b/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php
@@ -0,0 +1,39 @@
+input = $input;
+ $this->parser = new MimeDir($input, $options);
+
+ }
+
+ /**
+ * Every time getNext() is called, a new object will be parsed, until we
+ * hit the end of the stream.
+ *
+ * When the end is reached, null will be returned.
+ *
+ * @return Sabre\VObject\Component|null
+ */
+ public function getNext() {
+
+ try {
+ $object = $this->parser->parse();
+
+ if (!$object instanceof VObject\Component\VCard) {
+ throw new VObject\ParseException('The supplied input contained non-VCARD data.');
+ }
+
+ } catch (VObject\EofException $e) {
+ return null;
+ }
+
+ return $object;
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/StringUtil.php b/vendor/sabre/vobject/lib/StringUtil.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/StringUtil.php
@@ -0,0 +1,65 @@
+ 'UTC',
+ 31 => 'Africa/Casablanca',
+
+ // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
+ // I'm not even kidding.. We handle this special case in the
+ // getTimeZone method.
+ 2 => 'Europe/Lisbon',
+ 1 => 'Europe/London',
+ 4 => 'Europe/Berlin',
+ 6 => 'Europe/Prague',
+ 3 => 'Europe/Paris',
+ 69 => 'Africa/Luanda', // This was a best guess
+ 7 => 'Europe/Athens',
+ 5 => 'Europe/Bucharest',
+ 49 => 'Africa/Cairo',
+ 50 => 'Africa/Harare',
+ 59 => 'Europe/Helsinki',
+ 27 => 'Asia/Jerusalem',
+ 26 => 'Asia/Baghdad',
+ 74 => 'Asia/Kuwait',
+ 51 => 'Europe/Moscow',
+ 56 => 'Africa/Nairobi',
+ 25 => 'Asia/Tehran',
+ 24 => 'Asia/Muscat', // Best guess
+ 54 => 'Asia/Baku',
+ 48 => 'Asia/Kabul',
+ 58 => 'Asia/Yekaterinburg',
+ 47 => 'Asia/Karachi',
+ 23 => 'Asia/Calcutta',
+ 62 => 'Asia/Kathmandu',
+ 46 => 'Asia/Almaty',
+ 71 => 'Asia/Dhaka',
+ 66 => 'Asia/Colombo',
+ 61 => 'Asia/Rangoon',
+ 22 => 'Asia/Bangkok',
+ 64 => 'Asia/Krasnoyarsk',
+ 45 => 'Asia/Shanghai',
+ 63 => 'Asia/Irkutsk',
+ 21 => 'Asia/Singapore',
+ 73 => 'Australia/Perth',
+ 75 => 'Asia/Taipei',
+ 20 => 'Asia/Tokyo',
+ 72 => 'Asia/Seoul',
+ 70 => 'Asia/Yakutsk',
+ 19 => 'Australia/Adelaide',
+ 44 => 'Australia/Darwin',
+ 18 => 'Australia/Brisbane',
+ 76 => 'Australia/Sydney',
+ 43 => 'Pacific/Guam',
+ 42 => 'Australia/Hobart',
+ 68 => 'Asia/Vladivostok',
+ 41 => 'Asia/Magadan',
+ 17 => 'Pacific/Auckland',
+ 40 => 'Pacific/Fiji',
+ 67 => 'Pacific/Tongatapu',
+ 29 => 'Atlantic/Azores',
+ 53 => 'Atlantic/Cape_Verde',
+ 30 => 'America/Noronha',
+ 8 => 'America/Sao_Paulo', // Best guess
+ 32 => 'America/Argentina/Buenos_Aires',
+ 60 => 'America/Godthab',
+ 28 => 'America/St_Johns',
+ 9 => 'America/Halifax',
+ 33 => 'America/Caracas',
+ 65 => 'America/Santiago',
+ 35 => 'America/Bogota',
+ 10 => 'America/New_York',
+ 34 => 'America/Indiana/Indianapolis',
+ 55 => 'America/Guatemala',
+ 11 => 'America/Chicago',
+ 37 => 'America/Mexico_City',
+ 36 => 'America/Edmonton',
+ 38 => 'America/Phoenix',
+ 12 => 'America/Denver', // Best guess
+ 13 => 'America/Los_Angeles', // Best guess
+ 14 => 'America/Anchorage',
+ 15 => 'Pacific/Honolulu',
+ 16 => 'Pacific/Midway',
+ 39 => 'Pacific/Kwajalein',
+ );
+
+ /**
+ * This method will try to find out the correct timezone for an iCalendar
+ * date-time value.
+ *
+ * You must pass the contents of the TZID parameter, as well as the full
+ * calendar.
+ *
+ * If the lookup fails, this method will return the default PHP timezone
+ * (as configured using date_default_timezone_set, or the date.timezone ini
+ * setting).
+ *
+ * Alternatively, if $failIfUncertain is set to true, it will throw an
+ * exception if we cannot accurately determine the timezone.
+ *
+ * @param string $tzid
+ * @param Sabre\VObject\Component $vcalendar
+ * @return DateTimeZone
+ */
+ static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
+
+ // First we will just see if the tzid is a support timezone identifier.
+ //
+ // The only exception is if the timezone starts with (. This is to
+ // handle cases where certain microsoft products generate timezone
+ // identifiers that for instance look like:
+ //
+ // (GMT+01.00) Sarajevo/Warsaw/Zagreb
+ //
+ // Since PHP 5.5.10, the first bit will be used as the timezone and
+ // this method will return just GMT+01:00. This is wrong, because it
+ // doesn't take DST into account.
+ if ($tzid[0]!=='(') {
+
+ // PHP has a bug that logs PHP warnings even it shouldn't:
+ // https://bugs.php.net/bug.php?id=67881
+ //
+ // That's why we're checking if we'll be able to successfull instantiate
+ // \DateTimeZone() before doing so. Otherwise we could simply instantiate
+ // and catch the exception.
+ $tzIdentifiers = \DateTimeZone::listIdentifiers();
+
+ try {
+ if (
+ (in_array($tzid, $tzIdentifiers)) ||
+ (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
+ (in_array($tzid, self::getIdentifiersBC()))
+ ) {
+ return new \DateTimeZone($tzid);
+ }
+ } catch(\Exception $e) {
+ }
+
+ }
+
+ self::loadTzMaps();
+
+ // Next, we check if the tzid is somewhere in our tzid map.
+ if (isset(self::$map[$tzid])) {
+ return new \DateTimeZone(self::$map[$tzid]);
+ }
+
+ // Maybe the author was hyper-lazy and just included an offset. We
+ // support it, but we aren't happy about it.
+ if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
+
+ // Note that the path in the source will never be taken from PHP 5.5.10
+ // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
+ // already gets returned early in this function. Once we drop support
+ // for versions under PHP 5.5.10, this bit can be taken out of the
+ // source.
+ // @codeCoverageIgnoreStart
+ return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
+ // @codeCoverageIgnoreEnd
+ }
+
+ if ($vcalendar) {
+
+ // If that didn't work, we will scan VTIMEZONE objects
+ foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
+
+ if ((string)$vtimezone->TZID === $tzid) {
+
+ // Some clients add 'X-LIC-LOCATION' with the olson name.
+ if (isset($vtimezone->{'X-LIC-LOCATION'})) {
+
+ $lic = (string)$vtimezone->{'X-LIC-LOCATION'};
+
+ // Libical generators may specify strings like
+ // "SystemV/EST5EDT". For those we must remove the
+ // SystemV part.
+ if (substr($lic,0,8)==='SystemV/') {
+ $lic = substr($lic,8);
+ }
+
+ return self::getTimeZone($lic, null, $failIfUncertain);
+
+ }
+ // Microsoft may add a magic number, which we also have an
+ // answer for.
+ if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
+ $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
+
+ // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
+ if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
+ return new \DateTimeZone('Europe/Sarajevo');
+ }
+
+ if (isset(self::$microsoftExchangeMap[$cdoId])) {
+ return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ if ($failIfUncertain) {
+ throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
+ }
+
+ // If we got all the way here, we default to UTC.
+ return new \DateTimeZone(date_default_timezone_get());
+
+ }
+
+ /**
+ * This method will load in all the tz mapping information, if it's not yet
+ * done.
+ */
+ static public function loadTzMaps() {
+
+ if (!is_null(self::$map)) return;
+
+ self::$map = array_merge(
+ include __DIR__ . '/timezonedata/windowszones.php',
+ include __DIR__ . '/timezonedata/lotuszones.php',
+ include __DIR__ . '/timezonedata/exchangezones.php',
+ include __DIR__ . '/timezonedata/php-workaround.php'
+ );
+
+ }
+
+ /**
+ * This method returns an array of timezone identifiers, that are supported
+ * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers()
+ *
+ * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
+ * - It's not supported by some PHP versions as well as HHVM.
+ * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
+ * (See timezonedata/php-bc.php and timezonedata php-workaround.php)
+ *
+ * @return array
+ */
+ static public function getIdentifiersBC() {
+ return include __DIR__ . '/timezonedata/php-bc.php';
+ }
+
+}
diff --git a/vendor/sabre/vobject/lib/UUIDUtil.php b/vendor/sabre/vobject/lib/UUIDUtil.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/UUIDUtil.php
@@ -0,0 +1,67 @@
+getDocumentType();
+ if ($inputVersion===$targetVersion) {
+ return clone $input;
+ }
+
+ if (!in_array($inputVersion, array(Document::VCARD21, Document::VCARD30, Document::VCARD40))) {
+ throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
+ }
+ if (!in_array($targetVersion, array(Document::VCARD30, Document::VCARD40))) {
+ throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
+ }
+
+ $newVersion = $targetVersion===Document::VCARD40?'4.0':'3.0';
+
+ $output = new Component\VCard(array(
+ 'VERSION' => $newVersion,
+ ));
+
+ foreach($input->children as $property) {
+
+ $this->convertProperty($input, $output, $property, $targetVersion);
+
+ }
+
+ return $output;
+
+ }
+
+ /**
+ * Handles conversion of a single property.
+ *
+ * @param Component\VCard $input
+ * @param Component\VCard $output
+ * @param Property $property
+ * @param int $targetVersion
+ * @return void
+ */
+ protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {
+
+ // Skipping these, those are automatically added.
+ if (in_array($property->name, array('VERSION', 'PRODID'))) {
+ return;
+ }
+
+ $parameters = $property->parameters();
+ $valueType = null;
+ if (isset($parameters['VALUE'])) {
+ $valueType = $parameters['VALUE']->getValue();
+ unset($parameters['VALUE']);
+ }
+ if (!$valueType) {
+ $valueType = $property->getValueType();
+ }
+ $newProperty = $output->createProperty(
+ $property->name,
+ $property->getParts(),
+ array(), // parameters will get added a bit later.
+ $valueType
+ );
+
+
+ if ($targetVersion===Document::VCARD30) {
+
+ if ($property instanceof Property\Uri && in_array($property->name, array('PHOTO','LOGO','SOUND'))) {
+
+ $newProperty = $this->convertUriToBinary($output, $newProperty);
+
+ } elseif ($property instanceof Property\VCard\DateAndOrTime) {
+
+ // In vCard 4, the birth year may be optional. This is not the
+ // case for vCard 3. Apple has a workaround for this that
+ // allows applications that support Apple's extension still
+ // omit birthyears in vCard 3, but applications that do not
+ // support this, will just use a random birthyear. We're
+ // choosing 1604 for the birthyear, because that's what apple
+ // uses.
+ $parts = DateTimeParser::parseVCardDateTime($property->getValue());
+ if (is_null($parts['year'])) {
+ $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
+ $newProperty->setValue($newValue);
+ $newProperty['X-APPLE-OMIT-YEAR'] = '1604';
+ }
+
+ if ($newProperty->name == 'ANNIVERSARY') {
+ // Microsoft non-standard anniversary
+ $newProperty->name = 'X-ANNIVERSARY';
+
+ // We also need to add a new apple property for the same
+ // purpose. This apple property needs a 'label' in the same
+ // group, so we first need to find a groupname that doesn't
+ // exist yet.
+ $x = 1;
+ while($output->select('ITEM' . $x . '.')) {
+ $x++;
+ }
+ $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), array('VALUE' => 'DATE-AND-OR-TIME'));
+ $output->add('ITEM' . $x . '.X-ABLABEL', '_$!!$_');
+ }
+
+ } elseif ($property->name === 'KIND') {
+
+ switch(strtolower($property->getValue())) {
+ case 'org' :
+ // vCard 3.0 does not have an equivalent to KIND:ORG,
+ // but apple has an extension that means the same
+ // thing.
+ $newProperty = $output->createProperty('X-ABSHOWAS','COMPANY');
+ break;
+
+ case 'individual' :
+ // Individual is implicit, so we skip it.
+ return;
+
+ case 'group' :
+ // OS X addressbook property
+ $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND','GROUP');
+ break;
+ }
+
+
+ }
+
+ } elseif ($targetVersion===Document::VCARD40) {
+
+ // These properties were removed in vCard 4.0
+ if (in_array($property->name, array('NAME', 'MAILER', 'LABEL', 'CLASS'))) {
+ return;
+ }
+
+ if ($property instanceof Property\Binary) {
+
+ $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);
+
+ } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {
+
+ // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
+ // then we're stripping the year from the vcard 4 value.
+ $parts = DateTimeParser::parseVCardDateTime($property->getValue());
+ if ($parts['year']===$property['X-APPLE-OMIT-YEAR']->getValue()) {
+ $newValue = '--' . $parts['month'] . '-' . $parts['date'];
+ $newProperty->setValue($newValue);
+ }
+
+ // Regardless if the year matched or not, we do need to strip
+ // X-APPLE-OMIT-YEAR.
+ unset($parameters['X-APPLE-OMIT-YEAR']);
+
+ }
+ switch($property->name) {
+ case 'X-ABSHOWAS' :
+ if (strtoupper($property->getValue()) === 'COMPANY') {
+ $newProperty = $output->createProperty('KIND','ORG');
+ }
+ break;
+ case 'X-ADDRESSBOOKSERVER-KIND' :
+ if (strtoupper($property->getValue()) === 'GROUP') {
+ $newProperty = $output->createProperty('KIND','GROUP');
+ }
+ break;
+ case 'X-ANNIVERSARY' :
+ $newProperty->name = 'ANNIVERSARY';
+ // If we already have an anniversary property with the same
+ // value, ignore.
+ foreach ($output->select('ANNIVERSARY') as $anniversary) {
+ if ($anniversary->getValue() === $newProperty->getValue()) {
+ return;
+ }
+ }
+ break;
+ case 'X-ABDATE' :
+ // Find out what the label was, if it exists.
+ if (!$property->group) {
+ break;
+ }
+ $label = $input->{$property->group . '.X-ABLABEL'};
+
+ // We only support converting anniversaries.
+ if (!$label || $label->getValue()!=='_$!!$_') {
+ break;
+ }
+
+ // If we already have an anniversary property with the same
+ // value, ignore.
+ foreach ($output->select('ANNIVERSARY') as $anniversary) {
+ if ($anniversary->getValue() === $newProperty->getValue()) {
+ return;
+ }
+ }
+ $newProperty->name = 'ANNIVERSARY';
+ break;
+ // Apple's per-property label system.
+ case 'X-ABLABEL' :
+ if($newProperty->getValue() === '_$!!$_') {
+ // We can safely remove these, as they are converted to
+ // ANNIVERSARY properties.
+ return;
+ }
+ break;
+
+ }
+
+ }
+
+ // set property group
+ $newProperty->group = $property->group;
+
+ if ($targetVersion===Document::VCARD40) {
+ $this->convertParameters40($newProperty, $parameters);
+ } else {
+ $this->convertParameters30($newProperty, $parameters);
+ }
+
+ // Lastly, we need to see if there's a need for a VALUE parameter.
+ //
+ // We can do that by instantating a empty property with that name, and
+ // seeing if the default valueType is identical to the current one.
+ $tempProperty = $output->createProperty($newProperty->name);
+ if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
+ $newProperty['VALUE'] = $newProperty->getValueType();
+ }
+
+ $output->add($newProperty);
+
+
+ }
+
+ /**
+ * Converts a BINARY property to a URI property.
+ *
+ * vCard 4.0 no longer supports BINARY properties.
+ *
+ * @param Component\VCard $output
+ * @param Property\Uri $property The input property.
+ * @param $parameters List of parameters that will eventually be added to
+ * the new property.
+ * @return Property\Uri
+ */
+ protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) {
+
+ $value = $newProperty->getValue();
+ $newProperty = $output->createProperty(
+ $newProperty->name,
+ null, // no value
+ array(), // no parameters yet
+ 'URI' // Forcing the BINARY type
+ );
+
+ $mimeType = 'application/octet-stream';
+
+ // See if we can find a better mimetype.
+ if (isset($parameters['TYPE'])) {
+
+ $newTypes = array();
+ foreach($parameters['TYPE']->getParts() as $typePart) {
+ if (in_array(
+ strtoupper($typePart),
+ array('JPEG','PNG','GIF')
+ )) {
+ $mimeType = 'image/' . strtolower($typePart);
+ } else {
+ $newTypes[] = $typePart;
+ }
+ }
+
+ // If there were any parameters we're not converting to a
+ // mime-type, we need to keep them.
+ if ($newTypes) {
+ $parameters['TYPE']->setParts($newTypes);
+ } else {
+ unset($parameters['TYPE']);
+ }
+
+ }
+
+ $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
+ return $newProperty;
+
+ }
+
+ /**
+ * Converts a URI property to a BINARY property.
+ *
+ * In vCard 4.0 attachments are encoded as data: uri. Even though these may
+ * be valid in vCard 3.0 as well, we should convert those to BINARY if
+ * possible, to improve compatibility.
+ *
+ * @param Component\VCard $output
+ * @param Property\Uri $property The input property.
+ * @return Property\Binary|null
+ */
+ protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) {
+
+ $value = $newProperty->getValue();
+
+ // Only converting data: uris
+ if (substr($value, 0, 5)!=='data:') {
+ return $newProperty;
+ }
+
+ $newProperty = $output->createProperty(
+ $newProperty->name,
+ null, // no value
+ array(), // no parameters yet
+ 'BINARY'
+ );
+
+ $mimeType = substr($value, 5, strpos($value, ',')-5);
+ if (strpos($mimeType, ';')) {
+ $mimeType = substr($mimeType,0,strpos($mimeType, ';'));
+ $newProperty->setValue(base64_decode(substr($value, strpos($value,',')+1)));
+ } else {
+ $newProperty->setValue(substr($value, strpos($value,',')+1));
+ }
+ unset($value);
+
+ $newProperty['ENCODING'] = 'b';
+ switch($mimeType) {
+
+ case 'image/jpeg' :
+ $newProperty['TYPE'] = 'JPEG';
+ break;
+ case 'image/png' :
+ $newProperty['TYPE'] = 'PNG';
+ break;
+ case 'image/gif' :
+ $newProperty['TYPE'] = 'GIF';
+ break;
+
+ }
+
+
+ return $newProperty;
+
+ }
+
+ /**
+ * Adds parameters to a new property for vCard 4.0
+ *
+ * @param Property $newProperty
+ * @param array $parameters
+ * @return void
+ */
+ protected function convertParameters40(Property $newProperty, array $parameters) {
+
+ // Adding all parameters.
+ foreach($parameters as $param) {
+
+ // vCard 2.1 allowed parameters with no name
+ if ($param->noName) $param->noName = false;
+
+ switch($param->name) {
+
+ // We need to see if there's any TYPE=PREF, because in vCard 4
+ // that's now PREF=1.
+ case 'TYPE' :
+ foreach($param->getParts() as $paramPart) {
+
+ if (strtoupper($paramPart)==='PREF') {
+ $newProperty->add('PREF','1');
+ } else {
+ $newProperty->add($param->name, $paramPart);
+ }
+
+ }
+ break;
+ // These no longer exist in vCard 4
+ case 'ENCODING' :
+ case 'CHARSET' :
+ break;
+
+ default :
+ $newProperty->add($param->name, $param->getParts());
+ break;
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Adds parameters to a new property for vCard 3.0
+ *
+ * @param Property $newProperty
+ * @param array $parameters
+ * @return void
+ */
+ protected function convertParameters30(Property $newProperty, array $parameters) {
+
+ // Adding all parameters.
+ foreach($parameters as $param) {
+
+ // vCard 2.1 allowed parameters with no name
+ if ($param->noName) $param->noName = false;
+
+ switch($param->name) {
+
+ case 'ENCODING' :
+ // This value only existed in vCard 2.1, and should be
+ // removed for anything else.
+ if (strtoupper($param->getValue())!=='QUOTED-PRINTABLE') {
+ $newProperty->add($param->name, $param->getParts());
+ }
+ break;
+
+ /*
+ * Converting PREF=1 to TYPE=PREF.
+ *
+ * Any other PREF numbers we'll drop.
+ */
+ case 'PREF' :
+ if ($param->getValue()=='1') {
+ $newProperty->add('TYPE','PREF');
+ }
+ break;
+
+ default :
+ $newProperty->add($param->name, $param->getParts());
+ break;
+
+ }
+
+ }
+
+ }
+}
diff --git a/vendor/sabre/vobject/lib/Version.php b/vendor/sabre/vobject/lib/Version.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Version.php
@@ -0,0 +1,19 @@
+ 'UTC',
+ 'Casablanca, Monrovia' => 'Africa/Casablanca',
+ 'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London' => 'Europe/Lisbon',
+ 'Greenwich Mean Time; Dublin, Edinburgh, London' => 'Europe/London',
+ 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin',
+ 'Belgrade, Pozsony, Budapest, Ljubljana, Prague' => 'Europe/Prague',
+ 'Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris',
+ 'Paris, Madrid, Brussels, Copenhagen' => 'Europe/Paris',
+ 'Prague, Central Europe' => 'Europe/Prague',
+ 'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb' => 'Europe/Sarajevo',
+ 'West Central Africa' => 'Africa/Luanda', // This was a best guess
+ 'Athens, Istanbul, Minsk' => 'Europe/Athens',
+ 'Bucharest' => 'Europe/Bucharest',
+ 'Cairo' => 'Africa/Cairo',
+ 'Harare, Pretoria' => 'Africa/Harare',
+ 'Helsinki, Riga, Tallinn' => 'Europe/Helsinki',
+ 'Israel, Jerusalem Standard Time' => 'Asia/Jerusalem',
+ 'Baghdad' => 'Asia/Baghdad',
+ 'Arab, Kuwait, Riyadh' => 'Asia/Kuwait',
+ 'Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow',
+ 'East Africa, Nairobi' => 'Africa/Nairobi',
+ 'Tehran' => 'Asia/Tehran',
+ 'Abu Dhabi, Muscat' => 'Asia/Muscat', // Best guess
+ 'Baku, Tbilisi, Yerevan' => 'Asia/Baku',
+ 'Kabul' => 'Asia/Kabul',
+ 'Ekaterinburg' => 'Asia/Yekaterinburg',
+ 'Islamabad, Karachi, Tashkent' => 'Asia/Karachi',
+ 'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
+ 'Kathmandu, Nepal' => 'Asia/Kathmandu',
+ 'Almaty, Novosibirsk, North Central Asia' => 'Asia/Almaty',
+ 'Astana, Dhaka' => 'Asia/Dhaka',
+ 'Sri Jayawardenepura, Sri Lanka' => 'Asia/Colombo',
+ 'Rangoon' => 'Asia/Rangoon',
+ 'Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok',
+ 'Krasnoyarsk' => 'Asia/Krasnoyarsk',
+ 'Beijing, Chongqing, Hong Kong SAR, Urumqi' => 'Asia/Shanghai',
+ 'Irkutsk, Ulaan Bataar' => 'Asia/Irkutsk',
+ 'Kuala Lumpur, Singapore' => 'Asia/Singapore',
+ 'Perth, Western Australia' => 'Australia/Perth',
+ 'Taipei' => 'Asia/Taipei',
+ 'Osaka, Sapporo, Tokyo' => 'Asia/Tokyo',
+ 'Seoul, Korea Standard time' => 'Asia/Seoul',
+ 'Yakutsk' => 'Asia/Yakutsk',
+ 'Adelaide, Central Australia' => 'Australia/Adelaide',
+ 'Darwin' => 'Australia/Darwin',
+ 'Brisbane, East Australia' => 'Australia/Brisbane',
+ 'Canberra, Melbourne, Sydney, Hobart (year 2000 only)' => 'Australia/Sydney',
+ 'Guam, Port Moresby' => 'Pacific/Guam',
+ 'Hobart, Tasmania' => 'Australia/Hobart',
+ 'Vladivostok' => 'Asia/Vladivostok',
+ 'Magadan, Solomon Is., New Caledonia' => 'Asia/Magadan',
+ 'Auckland, Wellington' => 'Pacific/Auckland',
+ 'Fiji Islands, Kamchatka, Marshall Is.' => 'Pacific/Fiji',
+ 'Nuku\'alofa, Tonga' => 'Pacific/Tongatapu',
+ 'Azores' => 'Atlantic/Azores',
+ 'Cape Verde Is.' => 'Atlantic/Cape_Verde',
+ 'Mid-Atlantic' => 'America/Noronha',
+ 'Brasilia' => 'America/Sao_Paulo', // Best guess
+ 'Buenos Aires' => 'America/Argentina/Buenos_Aires',
+ 'Greenland' => 'America/Godthab',
+ 'Newfoundland' => 'America/St_Johns',
+ 'Atlantic Time (Canada)' => 'America/Halifax',
+ 'Caracas, La Paz' => 'America/Caracas',
+ 'Santiago' => 'America/Santiago',
+ 'Bogota, Lima, Quito' => 'America/Bogota',
+ 'Eastern Time (US & Canada)' => 'America/New_York',
+ 'Indiana (East)' => 'America/Indiana/Indianapolis',
+ 'Central America' => 'America/Guatemala',
+ 'Central Time (US & Canada)' => 'America/Chicago',
+ 'Mexico City, Tegucigalpa' => 'America/Mexico_City',
+ 'Saskatchewan' => 'America/Edmonton',
+ 'Arizona' => 'America/Phoenix',
+ 'Mountain Time (US & Canada)' => 'America/Denver', // Best guess
+ 'Pacific Time (US & Canada); Tijuana' => 'America/Los_Angeles', // Best guess
+ 'Alaska' => 'America/Anchorage',
+ 'Hawaii' => 'Pacific/Honolulu',
+ 'Midway Island, Samoa' => 'Pacific/Midway',
+ 'Eniwetok, Kwajalein, Dateline Time' => 'Pacific/Kwajalein',
+);
diff --git a/vendor/sabre/vobject/lib/timezonedata/lotuszones.php b/vendor/sabre/vobject/lib/timezonedata/lotuszones.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/timezonedata/lotuszones.php
@@ -0,0 +1,101 @@
+ 'Etc/GMT-12',
+ 'Samoa' => 'Pacific/Apia',
+ 'Hawaiian' => 'Pacific/Honolulu',
+ 'Alaskan' => 'America/Anchorage',
+ 'Pacific' => 'America/Los_Angeles',
+ 'Pacific Standard Time' => 'America/Los_Angeles',
+ 'Mexico Standard Time 2' => 'America/Chihuahua',
+ 'Mountain' => 'America/Denver',
+ // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones.
+ 'US Mountain' => 'America/Phoenix',
+ 'Canada Central' => 'America/Edmonton',
+ 'Central America' => 'America/Guatemala',
+ 'Central' => 'America/Chicago',
+ // 'Central Standard Time' => 'America/Mexico_City', // conflict with windows timezones.
+ 'Mexico' => 'America/Mexico_City',
+ 'Eastern' => 'America/New_York',
+ 'SA Pacific' => 'America/Bogota',
+ 'US Eastern' => 'America/Indiana/Indianapolis',
+ 'Venezuela' => 'America/Caracas',
+ 'Atlantic' => 'America/Halifax',
+ 'Central Brazilian' => 'America/Manaus',
+ 'Pacific SA' => 'America/Santiago',
+ 'SA Western' => 'America/La_Paz',
+ 'Newfoundland' => 'America/St_Johns',
+ 'Argentina' => 'America/Argentina/Buenos_Aires',
+ 'E. South America' => 'America/Belem',
+ 'Greenland' => 'America/Godthab',
+ 'Montevideo' => 'America/Montevideo',
+ 'SA Eastern' => 'America/Belem',
+ // 'Mid-Atlantic' => 'Etc/GMT-2', // conflict with windows timezones.
+ 'Azores' => 'Atlantic/Azores',
+ 'Cape Verde' => 'Atlantic/Cape_Verde',
+ 'Greenwich' => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
+ 'Morocco' => 'Africa/Casablanca',
+ 'Central Europe' => 'Europe/Prague',
+ 'Central European' => 'Europe/Sarajevo',
+ 'Romance' => 'Europe/Paris',
+ 'W. Central Africa' => 'Africa/Lagos', // Best guess
+ 'W. Europe' => 'Europe/Amsterdam',
+ 'E. Europe' => 'Europe/Minsk',
+ 'Egypt' => 'Africa/Cairo',
+ 'FLE' => 'Europe/Helsinki',
+ 'GTB' => 'Europe/Athens',
+ 'Israel' => 'Asia/Jerusalem',
+ 'Jordan' => 'Asia/Amman',
+ 'Middle East' => 'Asia/Beirut',
+ 'Namibia' => 'Africa/Windhoek',
+ 'South Africa' => 'Africa/Harare',
+ 'Arab' => 'Asia/Kuwait',
+ 'Arabic' => 'Asia/Baghdad',
+ 'E. Africa' => 'Africa/Nairobi',
+ 'Georgian' => 'Asia/Tbilisi',
+ 'Russian' => 'Europe/Moscow',
+ 'Iran' => 'Asia/Tehran',
+ 'Arabian' => 'Asia/Muscat',
+ 'Armenian' => 'Asia/Yerevan',
+ 'Azerbijan' => 'Asia/Baku',
+ 'Caucasus' => 'Asia/Yerevan',
+ 'Mauritius' => 'Indian/Mauritius',
+ 'Afghanistan' => 'Asia/Kabul',
+ 'Ekaterinburg' => 'Asia/Yekaterinburg',
+ 'Pakistan' => 'Asia/Karachi',
+ 'West Asia' => 'Asia/Tashkent',
+ 'India' => 'Asia/Calcutta',
+ 'Sri Lanka' => 'Asia/Colombo',
+ 'Nepal' => 'Asia/Kathmandu',
+ 'Central Asia' => 'Asia/Dhaka',
+ 'N. Central Asia' => 'Asia/Almaty',
+ 'Myanmar' => 'Asia/Rangoon',
+ 'North Asia' => 'Asia/Krasnoyarsk',
+ 'SE Asia' => 'Asia/Bangkok',
+ 'China' => 'Asia/Shanghai',
+ 'North Asia East' => 'Asia/Irkutsk',
+ 'Singapore' => 'Asia/Singapore',
+ 'Taipei' => 'Asia/Taipei',
+ 'W. Australia' => 'Australia/Perth',
+ 'Korea' => 'Asia/Seoul',
+ 'Tokyo' => 'Asia/Tokyo',
+ 'Yakutsk' => 'Asia/Yakutsk',
+ 'AUS Central' => 'Australia/Darwin',
+ 'Cen. Australia' => 'Australia/Adelaide',
+ 'AUS Eastern' => 'Australia/Sydney',
+ 'E. Australia' => 'Australia/Brisbane',
+ 'Tasmania' => 'Australia/Hobart',
+ 'Vladivostok' => 'Asia/Vladivostok',
+ 'West Pacific' => 'Pacific/Guam',
+ 'Central Pacific' => 'Asia/Magadan',
+ 'Fiji' => 'Pacific/Fiji',
+ 'New Zealand' => 'Pacific/Auckland',
+ 'Tonga' => 'Pacific/Tongatapu',
+);
diff --git a/vendor/sabre/vobject/lib/timezonedata/php-bc.php b/vendor/sabre/vobject/lib/timezonedata/php-bc.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/timezonedata/php-bc.php
@@ -0,0 +1,153 @@
+ 'America/Chicago',
+ 'Cuba' => 'America/Havana',
+ 'Egypt' => 'Africa/Cairo',
+ 'Eire' => 'Europe/Dublin',
+ 'EST5EDT' => 'America/New_York',
+ 'Factory' => 'UTC',
+ 'GB-Eire' => 'Europe/London',
+ 'GMT0' => 'UTC',
+ 'Greenwich' => 'UTC',
+ 'Hongkong' => 'Asia/Hong_Kong',
+ 'Iceland' => 'Atlantic/Reykjavik',
+ 'Iran' => 'Asia/Tehran',
+ 'Israel' => 'Asia/Jerusalem',
+ 'Jamaica' => 'America/Jamaica',
+ 'Japan' => 'Asia/Tokyo',
+ 'Kwajalein' => 'Pacific/Kwajalein',
+ 'Libya' => 'Africa/Tripoli',
+ 'MST7MDT' => 'America/Denver',
+ 'Navajo' => 'America/Denver',
+ 'NZ-CHAT' => 'Pacific/Chatham',
+ 'Poland' => 'Europe/Warsaw',
+ 'Portugal' => 'Europe/Lisbon',
+ 'PST8PDT' => 'America/Los_Angeles',
+ 'Singapore' => 'Asia/Singapore',
+ 'Turkey' => 'Europe/Istanbul',
+ 'Universal' => 'UTC',
+ 'W-SU' => 'Europe/Moscow',
+ 'Zulu' => 'UTC',
+);
diff --git a/vendor/sabre/vobject/lib/timezonedata/windowszones.php b/vendor/sabre/vobject/lib/timezonedata/windowszones.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/lib/timezonedata/windowszones.php
@@ -0,0 +1,118 @@
+ 'Australia/Darwin',
+ 'AUS Eastern Standard Time' => 'Australia/Sydney',
+ 'Afghanistan Standard Time' => 'Asia/Kabul',
+ 'Alaskan Standard Time' => 'America/Anchorage',
+ 'Arab Standard Time' => 'Asia/Riyadh',
+ 'Arabian Standard Time' => 'Asia/Dubai',
+ 'Arabic Standard Time' => 'Asia/Baghdad',
+ 'Argentina Standard Time' => 'America/Buenos_Aires',
+ 'Atlantic Standard Time' => 'America/Halifax',
+ 'Azerbaijan Standard Time' => 'Asia/Baku',
+ 'Azores Standard Time' => 'Atlantic/Azores',
+ 'Bahia Standard Time' => 'America/Bahia',
+ 'Bangladesh Standard Time' => 'Asia/Dhaka',
+ 'Belarus Standard Time' => 'Europe/Minsk',
+ 'Canada Central Standard Time' => 'America/Regina',
+ 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
+ 'Caucasus Standard Time' => 'Asia/Yerevan',
+ 'Cen. Australia Standard Time' => 'Australia/Adelaide',
+ 'Central America Standard Time' => 'America/Guatemala',
+ 'Central Asia Standard Time' => 'Asia/Almaty',
+ 'Central Brazilian Standard Time' => 'America/Cuiaba',
+ 'Central Europe Standard Time' => 'Europe/Budapest',
+ 'Central European Standard Time' => 'Europe/Warsaw',
+ 'Central Pacific Standard Time' => 'Pacific/Guadalcanal',
+ 'Central Standard Time' => 'America/Chicago',
+ 'Central Standard Time (Mexico)' => 'America/Mexico_City',
+ 'China Standard Time' => 'Asia/Shanghai',
+ 'Dateline Standard Time' => 'Etc/GMT+12',
+ 'E. Africa Standard Time' => 'Africa/Nairobi',
+ 'E. Australia Standard Time' => 'Australia/Brisbane',
+ 'E. South America Standard Time' => 'America/Sao_Paulo',
+ 'Eastern Standard Time' => 'America/New_York',
+ 'Egypt Standard Time' => 'Africa/Cairo',
+ 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg',
+ 'FLE Standard Time' => 'Europe/Kiev',
+ 'Fiji Standard Time' => 'Pacific/Fiji',
+ 'GMT Standard Time' => 'Europe/London',
+ 'GTB Standard Time' => 'Europe/Bucharest',
+ 'Georgian Standard Time' => 'Asia/Tbilisi',
+ 'Greenland Standard Time' => 'America/Godthab',
+ 'Greenwich Standard Time' => 'Atlantic/Reykjavik',
+ 'Hawaiian Standard Time' => 'Pacific/Honolulu',
+ 'India Standard Time' => 'Asia/Calcutta',
+ 'Iran Standard Time' => 'Asia/Tehran',
+ 'Israel Standard Time' => 'Asia/Jerusalem',
+ 'Jordan Standard Time' => 'Asia/Amman',
+ 'Kaliningrad Standard Time' => 'Europe/Kaliningrad',
+ 'Korea Standard Time' => 'Asia/Seoul',
+ 'Libya Standard Time' => 'Africa/Tripoli',
+ 'Line Islands Standard Time' => 'Pacific/Kiritimati',
+ 'Magadan Standard Time' => 'Asia/Magadan',
+ 'Mauritius Standard Time' => 'Indian/Mauritius',
+ 'Middle East Standard Time' => 'Asia/Beirut',
+ 'Montevideo Standard Time' => 'America/Montevideo',
+ 'Morocco Standard Time' => 'Africa/Casablanca',
+ 'Mountain Standard Time' => 'America/Denver',
+ 'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
+ 'Myanmar Standard Time' => 'Asia/Rangoon',
+ 'N. Central Asia Standard Time' => 'Asia/Novosibirsk',
+ 'Namibia Standard Time' => 'Africa/Windhoek',
+ 'Nepal Standard Time' => 'Asia/Katmandu',
+ 'New Zealand Standard Time' => 'Pacific/Auckland',
+ 'Newfoundland Standard Time' => 'America/St_Johns',
+ 'North Asia East Standard Time' => 'Asia/Irkutsk',
+ 'North Asia Standard Time' => 'Asia/Krasnoyarsk',
+ 'Pacific SA Standard Time' => 'America/Santiago',
+ 'Pacific Standard Time' => 'America/Los_Angeles',
+ 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel',
+ 'Pakistan Standard Time' => 'Asia/Karachi',
+ 'Paraguay Standard Time' => 'America/Asuncion',
+ 'Romance Standard Time' => 'Europe/Paris',
+ 'Russia Time Zone 10' => 'Asia/Srednekolymsk',
+ 'Russia Time Zone 11' => 'Asia/Kamchatka',
+ 'Russia Time Zone 3' => 'Europe/Samara',
+ 'Russian Standard Time' => 'Europe/Moscow',
+ 'SA Eastern Standard Time' => 'America/Cayenne',
+ 'SA Pacific Standard Time' => 'America/Bogota',
+ 'SA Western Standard Time' => 'America/La_Paz',
+ 'SE Asia Standard Time' => 'Asia/Bangkok',
+ 'Samoa Standard Time' => 'Pacific/Apia',
+ 'Singapore Standard Time' => 'Asia/Singapore',
+ 'South Africa Standard Time' => 'Africa/Johannesburg',
+ 'Sri Lanka Standard Time' => 'Asia/Colombo',
+ 'Syria Standard Time' => 'Asia/Damascus',
+ 'Taipei Standard Time' => 'Asia/Taipei',
+ 'Tasmania Standard Time' => 'Australia/Hobart',
+ 'Tokyo Standard Time' => 'Asia/Tokyo',
+ 'Tonga Standard Time' => 'Pacific/Tongatapu',
+ 'Turkey Standard Time' => 'Europe/Istanbul',
+ 'US Eastern Standard Time' => 'America/Indianapolis',
+ 'US Mountain Standard Time' => 'America/Phoenix',
+ 'UTC' => 'Etc/GMT',
+ 'UTC+12' => 'Etc/GMT-12',
+ 'UTC-02' => 'Etc/GMT+2',
+ 'UTC-11' => 'Etc/GMT+11',
+ 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar',
+ 'Venezuela Standard Time' => 'America/Caracas',
+ 'Vladivostok Standard Time' => 'Asia/Vladivostok',
+ 'W. Australia Standard Time' => 'Australia/Perth',
+ 'W. Central Africa Standard Time' => 'Africa/Lagos',
+ 'W. Europe Standard Time' => 'Europe/Berlin',
+ 'West Asia Standard Time' => 'Asia/Tashkent',
+ 'West Pacific Standard Time' => 'Pacific/Port_Moresby',
+ 'Yakutsk Standard Time' => 'Asia/Yakutsk',
+);
diff --git a/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php b/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php
@@ -0,0 +1,22 @@
+assertEquals($event, $obj->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/CliTest.php b/vendor/sabre/vobject/tests/VObject/CliTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/CliTest.php
@@ -0,0 +1,650 @@
+cli = new CliMock();
+ $this->cli->stderr = fopen('php://memory','r+');
+ $this->cli->stdout = fopen('php://memory','r+');
+
+ }
+
+ public function testInvalidArg() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', '--hi'))
+ );
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ }
+
+ public function testQuiet() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', '-q'))
+ );
+ $this->assertTrue($this->cli->quiet);
+
+ rewind($this->cli->stderr);
+ $this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr)));
+
+ }
+
+ public function testHelp() {
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', '-h'))
+ );
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ }
+
+ public function testFormat() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', '--format=jcard'))
+ );
+
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ $this->assertEquals('jcard', $this->cli->format);
+
+ }
+
+ public function testFormatInvalid() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', '--format=foo'))
+ );
+
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ $this->assertNull($this->cli->format);
+
+ }
+
+ public function testInputFormatInvalid() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', '--inputformat=foo'))
+ );
+
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ $this->assertNull($this->cli->format);
+
+ }
+
+
+ public function testNoInputFile() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', 'color'))
+ );
+
+ rewind($this->cli->stderr);
+ $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100);
+
+ }
+
+ public function testTooManyArgs() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', 'color', 'a', 'b', 'c'))
+ );
+
+ }
+
+ public function testUnknownCommand() {
+
+ $this->assertEquals(
+ 1,
+ $this->cli->main(array('vobject', 'foo', '-'))
+ );
+
+ }
+
+ public function testConvertJson() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', 'convert','--format=json', '-'))
+ );
+
+ rewind($this->cli->stdout);
+ $version = Version::VERSION;
+ $this->assertEquals(
+ '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject '. $version .'\/\/EN"],["fn",{},"text","Cowboy Henk"]]]',
+ stream_get_contents($this->cli->stdout)
+ );
+
+ }
+
+ public function testConvertJCardPretty() {
+
+ if (version_compare(PHP_VERSION, '5.4.0') < 0) {
+ $this->markTestSkipped('This test required PHP 5.4.0');
+ }
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', 'convert','--format=jcard', '--pretty', '-'))
+ );
+
+ rewind($this->cli->stdout);
+ $version = Version::VERSION;
+
+ // PHP 5.5.12 changed the output
+
+ $expected = <<assertStringStartsWith(
+ $expected,
+ stream_get_contents($this->cli->stdout)
+ );
+
+ }
+
+ public function testConvertJCalFail() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'convert','--format=jcal', '--inputformat=mimedir', '-'))
+ );
+
+ }
+
+ public function testConvertMimeDir() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-'))
+ );
+
+ rewind($this->cli->stdout);
+ $expected = <<assertEquals(
+ strtr($expected, array("\n"=>"\r\n")),
+ stream_get_contents($this->cli->stdout)
+ );
+
+ }
+
+ public function testConvertDefaultFormats() {
+
+ $inputStream = fopen('php://memory','r+');
+ $outputFile = SABRE_TEMPDIR . 'bar.json';
+
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'convert','foo.json',$outputFile))
+ );
+
+ $this->assertEquals('json', $this->cli->inputFormat);
+ $this->assertEquals('json', $this->cli->format);
+
+ }
+
+ public function testConvertDefaultFormats2() {
+
+ $outputFile = SABRE_TEMPDIR . 'bar.ics';
+
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'convert','foo.ics',$outputFile))
+ );
+
+ $this->assertEquals('mimedir', $this->cli->inputFormat);
+ $this->assertEquals('mimedir', $this->cli->format);
+
+ }
+
+ public function testVCard3040() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-'))
+ );
+
+ rewind($this->cli->stdout);
+
+ $version = Version::VERSION;
+ $expected = <<assertEquals(
+ strtr($expected, array("\n"=>"\r\n")),
+ stream_get_contents($this->cli->stdout)
+ );
+
+ }
+
+ public function testVCard4030() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ $this->assertEquals(
+ 0,
+ $this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-'))
+ );
+
+ $version = Version::VERSION;
+
+ rewind($this->cli->stdout);
+ $expected = <<assertEquals(
+ strtr($expected, array("\n"=>"\r\n")),
+ stream_get_contents($this->cli->stdout)
+ );
+
+ }
+
+ public function testVCard4021() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+
+ // vCard 2.1 is not supported yet, so this returns a failure.
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'convert','--format=vcard21', '--pretty', '-'))
+ );
+
+ }
+
+ function testValidate() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ $result = $this->cli->main(array('vobject', 'validate', '-'));
+
+ $this->assertEquals(
+ 0,
+ $result
+ );
+
+ }
+
+ function testValidateFail() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'validate', '-'))
+ );
+
+ }
+
+ function testValidateFail2() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'validate', '-'))
+ );
+
+ }
+
+ function testRepair() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+ $this->assertEquals(
+ 2,
+ $this->cli->main(array('vobject', 'repair', '-'))
+ );
+
+ rewind($this->cli->stdout);
+ $this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout));
+ }
+
+ function testRepairNothing() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+
+ $result = $this->cli->main(array('vobject', 'repair', '-'));
+
+ rewind($this->cli->stderr);
+ $error = stream_get_contents($this->cli->stderr);
+
+ $this->assertEquals(
+ 0,
+ $result,
+ "This should have been error free. stderr output:\n" . $error
+ );
+
+ }
+
+ /**
+ * Note: this is a very shallow test, doesn't dig into the actual output,
+ * but just makes sure there's no errors thrown.
+ *
+ * The colorizer is not a critical component, it's mostly a debugging tool.
+ */
+ function testColorCalendar() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ $version = Version::VERSION;
+
+ /**
+ * This object is not valid, but it's designed to hit every part of the
+ * colorizer source.
+ */
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+
+ $result = $this->cli->main(array('vobject', 'color', '-'));
+
+ rewind($this->cli->stderr);
+ $error = stream_get_contents($this->cli->stderr);
+
+ $this->assertEquals(
+ 0,
+ $result,
+ "This should have been error free. stderr output:\n" . $error
+ );
+
+ }
+
+ /**
+ * Note: this is a very shallow test, doesn't dig into the actual output,
+ * but just makes sure there's no errors thrown.
+ *
+ * The colorizer is not a critical component, it's mostly a debugging tool.
+ */
+ function testColorVCard() {
+
+ $inputStream = fopen('php://memory','r+');
+
+ $version = Version::VERSION;
+
+ /**
+ * This object is not valid, but it's designed to hit every part of the
+ * colorizer source.
+ */
+ fwrite($inputStream, <<cli->stdin = $inputStream;
+ // vCard 2.1 is not supported yet, so this returns a failure.
+
+ $result = $this->cli->main(array('vobject', 'color', '-'));
+
+ rewind($this->cli->stderr);
+ $error = stream_get_contents($this->cli->stderr);
+
+ $this->assertEquals(
+ 0,
+ $result,
+ "This should have been error free. stderr output:\n" . $error
+ );
+
+ }
+}
+
+class CliMock extends Cli {
+
+ public $log = array();
+
+ public $quiet = false;
+
+ public $format;
+
+ public $pretty;
+
+ public $stdin;
+
+ public $stdout;
+
+ public $stderr;
+
+ public $inputFormat;
+
+ public $outputFormat;
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php b/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php
@@ -0,0 +1,179 @@
+assertEquals($outcome, $valarm->isInTimeRange($start, $end));
+
+ }
+
+ public function timeRangeTestData() {
+
+ $tests = array();
+
+ $calendar = new VCalendar();
+
+ // Hard date and time
+ $valarm1 = $calendar->createComponent('VALARM');
+ $valarm1->add(
+ $calendar->createProperty('TRIGGER', '20120312T130000Z', array('VALUE' => 'DATE-TIME'))
+ );
+
+ $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+ $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+ // Relation to start time of event
+ $valarm2 = $calendar->createComponent('VALARM');
+ $valarm2->add(
+ $calendar->createProperty('TRIGGER', '-P1D', array('VALUE' => 'DURATION'))
+ );
+
+ $vevent2 = $calendar->createComponent('VEVENT');
+ $vevent2->DTSTART = '20120313T130000Z';
+ $vevent2->add($valarm2);
+
+ $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+ $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+ // Relation to end time of event
+ $valarm3 = $calendar->createComponent('VALARM');
+ $valarm3->add( $calendar->createProperty('TRIGGER', '-P1D', array('VALUE'=>'DURATION', 'RELATED' => 'END')) );
+
+ $vevent3 = $calendar->createComponent('VEVENT');
+ $vevent3->DTSTART = '20120301T130000Z';
+ $vevent3->DTEND = '20120401T130000Z';
+ $vevent3->add($valarm3);
+
+ $tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+ $tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+ // Relation to end time of todo
+ $valarm4 = $calendar->createComponent('VALARM');
+ $valarm4->TRIGGER = '-P1D';
+ $valarm4->TRIGGER['VALUE'] = 'DURATION';
+ $valarm4->TRIGGER['RELATED']= 'END';
+
+ $vtodo4 = $calendar->createComponent('VTODO');
+ $vtodo4->DTSTART = '20120301T130000Z';
+ $vtodo4->DUE = '20120401T130000Z';
+ $vtodo4->add($valarm4);
+
+ $tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+ $tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+ // Relation to start time of event + repeat
+ $valarm5 = $calendar->createComponent('VALARM');
+ $valarm5->TRIGGER = '-P1D';
+ $valarm5->TRIGGER['VALUE'] = 'DURATION';
+ $valarm5->REPEAT = 10;
+ $valarm5->DURATION = 'P1D';
+
+ $vevent5 = $calendar->createComponent('VEVENT');
+ $vevent5->DTSTART = '20120301T130000Z';
+ $vevent5->add($valarm5);
+
+ $tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true);
+
+ // Relation to start time of event + duration, but no repeat
+ $valarm6 = $calendar->createComponent('VALARM');
+ $valarm6->TRIGGER = '-P1D';
+ $valarm6->TRIGGER['VALUE'] = 'DURATION';
+ $valarm6->DURATION = 'P1D';
+
+ $vevent6 = $calendar->createComponent('VEVENT');
+ $vevent6->DTSTART = '20120313T130000Z';
+ $vevent6->add($valarm6);
+
+ $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true);
+ $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false);
+
+
+ // Relation to end time of event (DURATION instead of DTEND)
+ $valarm7 = $calendar->createComponent('VALARM');
+ $valarm7->TRIGGER = '-P1D';
+ $valarm7->TRIGGER['VALUE'] = 'DURATION';
+ $valarm7->TRIGGER['RELATED']= 'END';
+
+ $vevent7 = $calendar->createComponent('VEVENT');
+ $vevent7->DTSTART = '20120301T130000Z';
+ $vevent7->DURATION = 'P30D';
+ $vevent7->add($valarm7);
+
+ $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false);
+ $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true);
+
+ // Relation to end time of event (No DTEND or DURATION)
+ $valarm7 = $calendar->createComponent('VALARM');
+ $valarm7->TRIGGER = '-P1D';
+ $valarm7->TRIGGER['VALUE'] = 'DURATION';
+ $valarm7->TRIGGER['RELATED']= 'END';
+
+ $vevent7 = $calendar->createComponent('VEVENT');
+ $vevent7->DTSTART = '20120301T130000Z';
+ $vevent7->add($valarm7);
+
+ $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true);
+ $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false);
+
+
+ return $tests;
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testInTimeRangeInvalidComponent() {
+
+ $calendar = new VCalendar();
+ $valarm = $calendar->createComponent('VALARM');
+ $valarm->TRIGGER = '-P1D';
+ $valarm->TRIGGER['RELATED'] = 'END';
+
+ $vjournal = $calendar->createComponent('VJOURNAL');
+ $vjournal->add($valarm);
+
+ $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'));
+
+ }
+
+ /**
+ * This bug was found and reported on the mailing list.
+ */
+ public function testInTimeRangeBuggy() {
+
+$input = <<assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00')));
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php b/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php
@@ -0,0 +1,385 @@
+assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY);
+
+ }
+
+ function testRFCxxxSection3_1_availabilityprop_required() {
+
+ // UID and DTSTAMP are present.
+ $this->assertIsValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsValid(Reader::read($this->template($properties)));
+
+ // We duplicate each one to see if it fails.
+ foreach ($properties as $property) {
+ $this->assertIsNotValid(Reader::read($this->template(array(
+ $property,
+ $property
+ ))));
+ }
+
+ }
+
+ function testRFCxxxSection3_1_availabilityprop_dtend_duration() {
+
+ // Only DTEND.
+ $this->assertIsValid(Reader::read($this->template(array(
+ 'DTEND:21111005T133225Z'
+ ))));
+
+ // Only DURATION.
+ $this->assertIsValid(Reader::read($this->template(array(
+ 'DURATION:PT1H'
+ ))));
+
+ // Both (not allowed).
+ $this->assertIsNotValid(Reader::read($this->template(array(
+ 'DTEND:21111005T133225Z',
+ 'DURATION:PT1H'
+ ))));
+ }
+
+ function testAvailableSubComponent() {
+
+ $vcal = <<assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE);
+
+ }
+
+ function testRFCxxxSection3_1_availableprop_required() {
+
+ // UID, DTSTAMP and DTSTART are present.
+ $this->assertIsValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsNotValid(Reader::read(
+<<assertIsValid(Reader::read($this->templateAvailable(array(
+ 'DTEND:21111005T133225Z'
+ ))));
+
+ // Only DURATION.
+ $this->assertIsValid(Reader::read($this->templateAvailable(array(
+ 'DURATION:PT1H'
+ ))));
+
+ // Both (not allowed).
+ $this->assertIsNotValid(Reader::read($this->templateAvailable(array(
+ 'DTEND:21111005T133225Z',
+ 'DURATION:PT1H'
+ ))));
+ }
+
+ function testRFCxxxSection3_1_available_optional_once() {
+
+ $properties = array(
+ 'CREATED:20111005T135125Z',
+ 'DESCRIPTION:Long bla bla',
+ 'LAST-MODIFIED:20111005T135325Z',
+ 'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z',
+ 'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',
+ 'SUMMARY:Bla bla'
+ );
+
+ // They are all present, only once.
+ $this->assertIsValid(Reader::read($this->templateAvailable($properties)));
+
+ // We duplicate each one to see if it fails.
+ foreach ($properties as $property) {
+ $this->assertIsNotValid(Reader::read($this->templateAvailable(array(
+ $property,
+ $property
+ ))));
+ }
+
+ }
+ function testRFCxxxSection3_2() {
+
+ $this->assertEquals(
+ 'BUSY',
+ Reader::read($this->templateAvailable(array(
+ 'BUSYTYPE:BUSY'
+ )))
+ ->VAVAILABILITY
+ ->AVAILABLE
+ ->BUSYTYPE
+ ->getValue()
+ );
+
+ $this->assertEquals(
+ 'BUSY-UNAVAILABLE',
+ Reader::read($this->templateAvailable(array(
+ 'BUSYTYPE:BUSY-UNAVAILABLE'
+ )))
+ ->VAVAILABILITY
+ ->AVAILABLE
+ ->BUSYTYPE
+ ->getValue()
+ );
+
+ $this->assertEquals(
+ 'BUSY-TENTATIVE',
+ Reader::read($this->templateAvailable(array(
+ 'BUSYTYPE:BUSY-TENTATIVE'
+ )))
+ ->VAVAILABILITY
+ ->AVAILABLE
+ ->BUSYTYPE
+ ->getValue()
+ );
+
+ }
+
+ protected function assertIsValid(VObject\Document $document) {
+
+ $this->assertEmpty($document->validate());
+
+ }
+
+ protected function assertIsNotValid(VObject\Document $document) {
+
+ $this->assertNotEmpty($document->validate());
+
+ }
+
+ protected function template(array $properties) {
+
+ return $this->_template(
+ <<_template(
+ <<expand(
+ new \DateTime($start),
+ new \DateTime($end),
+ $timeZone
+ );
+
+ // This will normalize the output
+ $output = VObject\Reader::read($output)->serialize();
+
+ $this->assertEquals($output, $vcal->serialize());
+
+ }
+
+ public function expandData() {
+
+ $tests = array();
+
+ // No data
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+END:VCALENDAR
+';
+
+ $output = $input;
+ $tests[] = array($input,$output);
+
+
+ // Simple events
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla
+SUMMARY:InExpand
+DTSTART;VALUE=DATE:20111202
+END:VEVENT
+BEGIN:VEVENT
+UID:bla2
+SUMMARY:NotInExpand
+DTSTART;VALUE=DATE:20120101
+END:VEVENT
+END:VCALENDAR
+';
+
+ $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla
+SUMMARY:InExpand
+DTSTART;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+ $tests[] = array($input, $output);
+
+ // Removing timezone info
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Europe/Paris
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:bla4
+SUMMARY:RemoveTZ info
+DTSTART;TZID=Europe/Paris:20111203T130102
+END:VEVENT
+END:VCALENDAR
+';
+
+ $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla4
+SUMMARY:RemoveTZ info
+DTSTART:20111203T120102Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $tests[] = array($input, $output);
+
+ // Recurrence rule
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111125T120000Z
+DTEND:20111125T130000Z
+RRULE:FREQ=WEEKLY
+END:VEVENT
+END:VCALENDAR
+';
+
+ $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111202T120000Z
+DTEND:20111202T130000Z
+RECURRENCE-ID:20111202T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111209T120000Z
+DTEND:20111209T130000Z
+RECURRENCE-ID:20111209T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111216T120000Z
+DTEND:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111223T120000Z
+DTEND:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule
+DTSTART:20111230T120000Z
+DTEND:20111230T130000Z
+RECURRENCE-ID:20111230T120000Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $tests[] = array($input, $output);
+
+ // Recurrence rule + override
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111125T120000Z
+DTEND:20111125T130000Z
+RRULE:FREQ=WEEKLY
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+RECURRENCE-ID:20111209T120000Z
+DTSTART:20111209T140000Z
+DTEND:20111209T150000Z
+SUMMARY:Override!
+END:VEVENT
+END:VCALENDAR
+';
+
+ $output = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111202T120000Z
+DTEND:20111202T130000Z
+RECURRENCE-ID:20111202T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+RECURRENCE-ID:20111209T120000Z
+DTSTART:20111209T140000Z
+DTEND:20111209T150000Z
+SUMMARY:Override!
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111216T120000Z
+DTEND:20111216T130000Z
+RECURRENCE-ID:20111216T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111223T120000Z
+DTEND:20111223T130000Z
+RECURRENCE-ID:20111223T120000Z
+END:VEVENT
+BEGIN:VEVENT
+UID:bla6
+SUMMARY:Testing RRule2
+DTSTART:20111230T120000Z
+DTEND:20111230T130000Z
+RECURRENCE-ID:20111230T120000Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $tests[] = array($input, $output);
+
+ // Floating dates and times.
+ $input = <<expand(
+ new \DateTime('2011-12-01'),
+ new \DateTime('2011-12-31')
+ );
+
+ }
+
+ function testGetDocumentType() {
+
+ $vcard = new VCalendar();
+ $vcard->VERSION = '2.0';
+ $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType());
+
+ }
+
+ function testValidateCorrect() {
+
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+DTSTAMP:20140122T233226Z
+UID:foo
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(array(), $vcal->validate(), 'Got an error');
+
+ }
+
+ function testValidateNoVersion() {
+
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateWrongVersion() {
+
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:3.0
+PRODID:foo
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateNoProdId() {
+
+ $input = 'BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateDoubleCalScale() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+CALSCALE:GREGORIAN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateDoubleMethod() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateTwoMasterEvents() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(1, count($vcal->validate()));
+
+ }
+
+ function testValidateOneMasterEvent() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+ $this->assertEquals(0, count($vcal->validate()));
+
+ }
+
+ function testGetBaseComponent() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+SUMMARY:test
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+
+ $result = $vcal->getBaseComponent();
+ $this->assertEquals('test', $result->SUMMARY->getValue());
+
+ }
+
+ function testGetBaseComponentNoResult() {
+
+ $input = 'BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:foo
+METHOD:REQUEST
+BEGIN:VEVENT
+SUMMARY:test
+RECURRENCE-ID;VALUE=DATE:20111202
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20111202
+UID:foo
+DTSTAMP:20140122T234434Z
+RECURRENCE-ID;VALUE=DATE:20111202
+END:VEVENT
+END:VCALENDAR
+';
+
+ $vcal = VObject\Reader::read($input);
+
+ $result = $vcal->getBaseComponent();
+ $this->assertNull($result);
+
+ }
+
+ function testNoComponents() {
+
+ $input = <<assertValidate(
+ $input,
+ 0,
+ 3,
+ "An iCalendar object must have at least 1 component."
+ );
+
+ }
+
+ function testCalDAVNoComponents() {
+
+ $input = <<assertValidate(
+ $input,
+ VCalendar::PROFILE_CALDAV,
+ 3,
+ "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)."
+ );
+
+ }
+
+ function testCalDAVMultiUID() {
+
+ $input = <<assertValidate(
+ $input,
+ VCalendar::PROFILE_CALDAV,
+ 3,
+ "A calendar object on a CalDAV server may only have components with the same UID."
+ );
+
+ }
+
+ function testCalDAVMultiComponent() {
+
+ $input = <<assertValidate(
+ $input,
+ VCalendar::PROFILE_CALDAV,
+ 3,
+ "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)."
+ );
+
+ }
+
+ function testCalDAVMETHOD() {
+
+ $input = <<assertValidate(
+ $input,
+ VCalendar::PROFILE_CALDAV,
+ 3,
+ "A calendar object on a CalDAV server MUST NOT have a METHOD property."
+ );
+
+ }
+
+ function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) {
+
+ $vcal = VObject\Reader::read($ics);
+ $result = $vcal->validate($options);
+
+ $this->assertValidateResult($result, $expectedLevel, $expectedMessage);
+
+ }
+
+ function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
+
+ $messages = array();
+ foreach($input as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ if ($expectedLevel === 0) {
+ $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
+ } else {
+ $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
+
+ $this->assertEquals($expectedMessage, $input[0]['message']);
+ $this->assertEquals($expectedLevel, $input[0]['level']);
+ }
+
+ }
+
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php b/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php
@@ -0,0 +1,288 @@
+validate();
+
+ $warnMsg = array();
+ foreach($warnings as $warning) {
+ $warnMsg[] = $warning['message'];
+ }
+
+ $this->assertEquals($expectedWarnings, $warnMsg);
+
+ $vcard->validate(VObject\Component::REPAIR);
+
+ $this->assertEquals(
+ $expectedRepairedOutput,
+ $vcard->serialize()
+ );
+
+ }
+
+ public function validateData() {
+
+ $tests = array();
+
+ // Correct
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ array(),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ );
+
+ // No VERSION
+ $tests[] = array(
+ "BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ array(
+ 'VERSION MUST appear exactly once in a VCARD component',
+ ),
+ "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ );
+
+ // Unknown version
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ array(
+ 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
+ ),
+ "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n",
+ );
+
+ // No FN
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n",
+ array(
+ 'The FN property must appear in the VCARD component exactly 1 time',
+ ),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n",
+ );
+ // No FN, N fallback
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n",
+ array(
+ 'The FN property must appear in the VCARD component exactly 1 time',
+ ),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n",
+ );
+ // No FN, N fallback, no first name
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n",
+ array(
+ 'The FN property must appear in the VCARD component exactly 1 time',
+ ),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n",
+ );
+
+ // No FN, ORG fallback
+ $tests[] = array(
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nEND:VCARD\r\n",
+ array(
+ 'The FN property must appear in the VCARD component exactly 1 time',
+ ),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n",
+ );
+ return $tests;
+
+ }
+
+ function testGetDocumentType() {
+
+ $vcard = new VCard(array(), false);
+ $vcard->VERSION = '2.1';
+ $this->assertEquals(VCard::VCARD21, $vcard->getDocumentType());
+
+ $vcard = new VCard(array(), false);
+ $vcard->VERSION = '3.0';
+ $this->assertEquals(VCard::VCARD30, $vcard->getDocumentType());
+
+ $vcard = new VCard(array(), false);
+ $vcard->VERSION = '4.0';
+ $this->assertEquals(VCard::VCARD40, $vcard->getDocumentType());
+
+ $vcard = new VCard(array(), false);
+ $this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType());
+ }
+
+ function testPreferredNoPref() {
+
+ $vcard = <<assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue());
+
+ }
+
+ function testPreferredWithPref() {
+
+ $vcard = <<assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue());
+
+ }
+
+ function testPreferredWith40Pref() {
+
+ $vcard = <<assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue());
+
+ }
+
+ function testPreferredNotFound() {
+
+ $vcard = <<assertNull($vcard->preferred('EMAIL'));
+
+ }
+
+ function testNoUIDCardDAV() {
+
+ $vcard = <<assertValidate(
+ $vcard,
+ VCARD::PROFILE_CARDDAV,
+ 3,
+ 'vCards on CardDAV servers MUST have a UID property.'
+ );
+
+ }
+
+ function testNoUIDNoCardDAV() {
+
+ $vcard = <<assertValidate(
+ $vcard,
+ 0,
+ 2,
+ 'Adding a UID to a vCard property is recommended.'
+ );
+
+ }
+ function testNoUIDNoCardDAVRepair() {
+
+ $vcard = <<assertValidate(
+ $vcard,
+ VCARD::REPAIR,
+ 1,
+ 'Adding a UID to a vCard property is recommended.'
+ );
+
+ }
+
+ function testVCard21CardDAV() {
+
+ $vcard = <<assertValidate(
+ $vcard,
+ VCARD::PROFILE_CARDDAV,
+ 3,
+ 'CardDAV servers are not allowed to accept vCard 2.1.'
+ );
+
+ }
+
+ function testVCard21NoCardDAV() {
+
+ $vcard = <<assertValidate(
+ $vcard,
+ 0,
+ 0
+ );
+
+ }
+
+ function assertValidate($vcf, $options, $expectedLevel, $expectedMessage = null) {
+
+ $vcal = VObject\Reader::read($vcf);
+ $result = $vcal->validate($options);
+
+ $this->assertValidateResult($result, $expectedLevel, $expectedMessage);
+
+ }
+
+ function assertValidateResult($input, $expectedLevel, $expectedMessage = null) {
+
+ $messages = array();
+ foreach($input as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ if ($expectedLevel === 0) {
+ $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages));
+ } else {
+ $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages));
+
+ $this->assertEquals($expectedMessage, $input[0]['message']);
+ $this->assertEquals($expectedLevel, $input[0]['level']);
+ }
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php b/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php
@@ -0,0 +1,90 @@
+assertEquals($outcome, $vevent->isInTimeRange($start, $end));
+
+ }
+
+ public function timeRangeTestData() {
+
+ $tests = array();
+
+ $calendar = new VCalendar();
+
+ $vevent = $calendar->createComponent('VEVENT');
+ $vevent->DTSTART = '20111223T120000Z';
+ $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vevent2 = clone $vevent;
+ $vevent2->DTEND = '20111225T120000Z';
+ $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vevent3 = clone $vevent;
+ $vevent3->DURATION = 'P1D';
+ $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vevent4 = clone $vevent;
+ $vevent4->DTSTART = '20111225';
+ $vevent4->DTSTART['VALUE'] = 'DATE';
+ $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+ // Event with no end date should be treated as lasting the entire day.
+ $tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true);
+ // DTEND is non inclusive so all day events should not be returned on the next day.
+ $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00'), new \DateTime('2011-12-26 17:00:00'), false);
+ // The timezone of timerange in question also needs to be considered.
+ $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2011-12-26 17:00:00', new \DateTimeZone('Europe/Berlin')), false);
+
+ $vevent5 = clone $vevent;
+ $vevent5->DURATION = 'P1D';
+ $vevent5->RRULE = 'FREQ=YEARLY';
+ $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+ $tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true);
+
+ $vevent6 = clone $vevent;
+ $vevent6->DTSTART = '20111225';
+ $vevent6->DTSTART['VALUE'] = 'DATE';
+ $vevent6->DTEND = '20111225';
+ $vevent6->DTEND['VALUE'] = 'DATE';
+
+ $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ // Added this test to ensure that recurrence rules with no DTEND also
+ // get checked for the entire day.
+ $vevent7 = clone $vevent;
+ $vevent7->DTSTART = '20120101';
+ $vevent7->DTSTART['VALUE'] = 'DATE';
+ $vevent7->RRULE = 'FREQ=MONTHLY';
+ $tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true);
+ // The timezone of timerange in question should also be considered.
+
+ // Added this test to check recurring events that have no instances.
+ $vevent8 = clone $vevent;
+ $vevent8->DTSTART = '20130329T140000';
+ $vevent8->DTEND = '20130329T153000';
+ $vevent8->RRULE = array('FREQ' => 'WEEKLY', 'BYDAY' => array('FR'), 'UNTIL' => '20130412T115959Z');
+ $vevent8->add('EXDATE', '20130405T140000');
+ $vevent8->add('EXDATE', '20130329T140000');
+ $tests[] = array($vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false);
+
+ return $tests;
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php b/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php
@@ -0,0 +1,66 @@
+VFREEBUSY;
+
+ $tz = new \DateTimeZone('UTC');
+
+ $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz)));
+ $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz)));
+ $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz)));
+
+ // Checking whether the end time is treated as non-inclusive
+ $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz)));
+ $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz)));
+ $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz)));
+
+ }
+
+ public function testValidate() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(), $messages);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php b/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php
@@ -0,0 +1,101 @@
+assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
+
+ }
+
+ public function testValidate() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(), $messages);
+
+ }
+
+ public function testValidateBroken() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(
+ array("URL MUST NOT appear more than once in a VJOURNAL component"),
+ $messages
+ );
+
+ }
+
+ public function timeRangeTestData() {
+
+ $calendar = new VCalendar();
+
+ $tests = array();
+
+ $vjournal = $calendar->createComponent('VJOURNAL');
+ $vjournal->DTSTART = '20111223T120000Z';
+ $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vjournal2 = $calendar->createComponent('VJOURNAL');
+ $vjournal2->DTSTART = '20111223';
+ $vjournal2->DTSTART['VALUE'] = 'DATE';
+ $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vjournal3 = $calendar->createComponent('VJOURNAL');
+ $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false);
+ $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ return $tests;
+ }
+
+
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php b/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php
@@ -0,0 +1,57 @@
+validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(), $messages);
+
+ }
+
+ function testGetTimeZone() {
+
+ $input = <<assertEquals(
+ $tz,
+ $obj->VTIMEZONE->getTimeZone()
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php b/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php
@@ -0,0 +1,180 @@
+assertEquals($outcome, $vtodo->isInTimeRange($start, $end));
+
+ }
+
+ public function timeRangeTestData() {
+
+ $tests = array();
+
+ $calendar = new VCalendar();
+
+ $vtodo = $calendar->createComponent('VTODO');
+ $vtodo->DTSTART = '20111223T120000Z';
+ $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo2 = clone $vtodo;
+ $vtodo2->DURATION = 'P1D';
+ $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo3 = clone $vtodo;
+ $vtodo3->DUE = '20111225';
+ $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo4 = $calendar->createComponent('VTODO');
+ $vtodo4->DUE = '20111225';
+ $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo5 = $calendar->createComponent('VTODO');
+ $vtodo5->COMPLETED = '20111225';
+ $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo6 = $calendar->createComponent('VTODO');
+ $vtodo6->CREATED = '20111225';
+ $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo7 = $calendar->createComponent('VTODO');
+ $vtodo7->CREATED = '20111225';
+ $vtodo7->COMPLETED = '20111226';
+ $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false);
+
+ $vtodo7 = $calendar->createComponent('VTODO');
+ $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true);
+ $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true);
+
+ return $tests;
+
+ }
+
+ public function testValidate() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(), $messages);
+
+ }
+
+ public function testValidateInvalid() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(
+ "UID MUST appear exactly once in a VTODO component",
+ "DTSTAMP MUST appear exactly once in a VTODO component",
+ ), $messages);
+
+ }
+
+ public function testValidateDUEDTSTARTMisMatch() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(
+ "The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART",
+ ), $messages);
+
+ }
+
+ public function testValidateDUEbeforeDTSTART() {
+
+ $input = <<validate();
+ $messages = array();
+ foreach($warnings as $warning) {
+ $messages[] = $warning['message'];
+ }
+
+ $this->assertEquals(array(
+ "DUE must occur after DTSTART",
+ ), $messages);
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/ComponentTest.php b/vendor/sabre/vobject/tests/VObject/ComponentTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ComponentTest.php
@@ -0,0 +1,528 @@
+createComponent('VEVENT');
+ $comp->add($sub);
+
+ $sub = $comp->createComponent('VTODO');
+ $comp->add($sub);
+
+ $count = 0;
+ foreach($comp->children() as $key=>$subcomponent) {
+
+ $count++;
+ $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
+
+ }
+ $this->assertEquals(2,$count);
+ $this->assertEquals(1,$key);
+
+ }
+
+ function testMagicGet() {
+
+ $comp = new VCalendar(array(), false);
+
+ $sub = $comp->createComponent('VEVENT');
+ $comp->add($sub);
+
+ $sub = $comp->createComponent('VTODO');
+ $comp->add($sub);
+
+ $event = $comp->vevent;
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $event);
+ $this->assertEquals('VEVENT', $event->name);
+
+ $this->assertInternalType('null', $comp->vjournal);
+
+ }
+
+ function testMagicGetGroups() {
+
+ $comp = new VCard();
+
+ $sub = $comp->createProperty('GROUP1.EMAIL','1@1.com');
+ $comp->add($sub);
+
+ $sub = $comp->createProperty('GROUP2.EMAIL','2@2.com');
+ $comp->add($sub);
+
+ $sub = $comp->createProperty('EMAIL','3@3.com');
+ $comp->add($sub);
+
+ $emails = $comp->email;
+ $this->assertEquals(3, count($emails));
+
+ $email1 = $comp->{"group1.email"};
+ $this->assertEquals('EMAIL', $email1[0]->name);
+ $this->assertEquals('GROUP1', $email1[0]->group);
+
+ $email3 = $comp->{".email"};
+ $this->assertEquals('EMAIL', $email3[0]->name);
+ $this->assertEquals(null, $email3[0]->group);
+
+ }
+
+ function testMagicIsset() {
+
+ $comp = new VCalendar();
+
+ $sub = $comp->createComponent('VEVENT');
+ $comp->add($sub);
+
+ $sub = $comp->createComponent('VTODO');
+ $comp->add($sub);
+
+ $this->assertTrue(isset($comp->vevent));
+ $this->assertTrue(isset($comp->vtodo));
+ $this->assertFalse(isset($comp->vjournal));
+
+ }
+
+ function testMagicSetScalar() {
+
+ $comp = new VCalendar();
+ $comp->myProp = 'myValue';
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+ $this->assertEquals('myValue',(string)$comp->MYPROP);
+
+
+ }
+
+ function testMagicSetScalarTwice() {
+
+ $comp = new VCalendar(array(), false);
+ $comp->myProp = 'myValue';
+ $comp->myProp = 'myValue';
+
+ $this->assertEquals(1,count($comp->children()));
+ $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP);
+ $this->assertEquals('myValue',(string)$comp->MYPROP);
+
+ }
+
+ function testMagicSetArray() {
+
+ $comp = new VCalendar();
+ $comp->ORG = array('Acme Inc', 'Section 9');
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->ORG);
+ $this->assertEquals(array('Acme Inc', 'Section 9'),$comp->ORG->getParts());
+
+ }
+
+ function testMagicSetComponent() {
+
+ $comp = new VCalendar();
+
+ // Note that 'myProp' is ignored here.
+ $comp->myProp = $comp->createComponent('VEVENT');
+
+ $this->assertEquals(1, count($comp));
+
+ $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+ }
+
+ function testMagicSetTwice() {
+
+ $comp = new VCalendar(array(), false);
+
+ $comp->VEVENT = $comp->createComponent('VEVENT');
+ $comp->VEVENT = $comp->createComponent('VEVENT');
+
+ $this->assertEquals(1, count($comp->children()));
+
+ $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+ }
+
+ function testArrayAccessGet() {
+
+ $comp = new VCalendar(array(), false);
+
+ $event = $comp->createComponent('VEVENT');
+ $event->summary = 'Event 1';
+
+ $comp->add($event);
+
+ $event2 = clone $event;
+ $event2->summary = 'Event 2';
+
+ $comp->add($event2);
+
+ $this->assertEquals(2,count($comp->children()));
+ $this->assertTrue($comp->vevent[1] instanceof Component);
+ $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary);
+
+ }
+
+ function testArrayAccessExists() {
+
+ $comp = new VCalendar();
+
+ $event = $comp->createComponent('VEVENT');
+ $event->summary = 'Event 1';
+
+ $comp->add($event);
+
+ $event2 = clone $event;
+ $event2->summary = 'Event 2';
+
+ $comp->add($event2);
+
+ $this->assertTrue(isset($comp->vevent[0]));
+ $this->assertTrue(isset($comp->vevent[1]));
+
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ function testArrayAccessSet() {
+
+ $comp = new VCalendar();
+ $comp['hey'] = 'hi there';
+
+ }
+ /**
+ * @expectedException LogicException
+ */
+ function testArrayAccessUnset() {
+
+ $comp = new VCalendar();
+ unset($comp[0]);
+
+ }
+
+ function testAddScalar() {
+
+ $comp = new VCalendar(array(), false);
+
+ $comp->add('myprop','value');
+
+ $this->assertEquals(1, count($comp->children()));
+
+ $bla = $comp->children[0];
+
+ $this->assertTrue($bla instanceof Property);
+ $this->assertEquals('MYPROP',$bla->name);
+ $this->assertEquals('value',(string)$bla);
+
+ }
+
+ function testAddScalarParams() {
+
+ $comp = new VCalendar(array(), false);
+
+ $comp->add('myprop','value',array('param1'=>'value1'));
+
+ $this->assertEquals(1, count($comp->children()));
+
+ $bla = $comp->children[0];
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $bla);
+ $this->assertEquals('MYPROP',$bla->name);
+ $this->assertEquals('value', (string)$bla);
+
+ $this->assertEquals(1, count($bla->parameters()));
+
+ $this->assertEquals('PARAM1',$bla->parameters['PARAM1']->name);
+ $this->assertEquals('value1',$bla->parameters['PARAM1']->getValue());
+
+ }
+
+
+ function testAddComponent() {
+
+ $comp = new VCalendar(array(), false);
+
+ $comp->add($comp->createComponent('VEVENT'));
+
+ $this->assertEquals(1, count($comp->children()));
+
+ $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+ }
+
+ function testAddComponentTwice() {
+
+ $comp = new VCalendar(array(), false);
+
+ $comp->add($comp->createComponent('VEVENT'));
+ $comp->add($comp->createComponent('VEVENT'));
+
+ $this->assertEquals(2, count($comp->children()));
+
+ $this->assertEquals('VEVENT',$comp->VEVENT->name);
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testAddArgFail() {
+
+ $comp = new VCalendar();
+ $comp->add($comp->createComponent('VEVENT'),'hello');
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testAddArgFail2() {
+
+ $comp = new VCalendar();
+ $comp->add(array());
+
+ }
+
+ function testMagicUnset() {
+
+ $comp = new VCalendar(array(), false);
+ $comp->add($comp->createComponent('VEVENT'));
+
+ unset($comp->vevent);
+
+ $this->assertEquals(0, count($comp->children()));
+
+ }
+
+
+ function testCount() {
+
+ $comp = new VCalendar();
+ $this->assertEquals(1,$comp->count());
+
+ }
+
+ function testChildren() {
+
+ $comp = new VCalendar(array(), false);
+
+ // Note that 'myProp' is ignored here.
+ $comp->add($comp->createComponent('VEVENT'));
+ $comp->add($comp->createComponent('VTODO'));
+
+ $r = $comp->children();
+ $this->assertInternalType('array', $r);
+ $this->assertEquals(2,count($r));
+ }
+
+ function testGetComponents() {
+
+ $comp = new VCalendar();
+
+ $comp->add($comp->createProperty('FOO','BAR'));
+ $comp->add($comp->createComponent('VTODO'));
+
+ $r = $comp->getComponents();
+ $this->assertInternalType('array', $r);
+ $this->assertEquals(1, count($r));
+ $this->assertEquals('VTODO', $r[0]->name);
+ }
+
+ function testSerialize() {
+
+ $comp = new VCalendar(array(), false);
+ $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize());
+
+ }
+
+ function testSerializeChildren() {
+
+ $comp = new VCalendar(array(), false);
+ $event = $comp->add($comp->createComponent('VEVENT'));
+ unset($event->DTSTAMP, $event->UID);
+ $comp->add($comp->createComponent('VTODO'));
+
+ $str = $comp->serialize();
+
+ $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str);
+
+ }
+
+ function testSerializeOrderCompAndProp() {
+
+ $comp = new VCalendar(array(), false);
+ $comp->add($event = $comp->createComponent('VEVENT'));
+ $comp->add('PROP1','BLABLA');
+ $comp->add('VERSION','2.0');
+ $comp->add($comp->createComponent('VTIMEZONE'));
+
+ unset($event->DTSTAMP, $event->UID);
+ $str = $comp->serialize();
+
+ $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str);
+
+ }
+
+ function testAnotherSerializeOrderProp() {
+
+ $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10');
+
+ $comp = new VCard(array(), false);
+
+ $comp->__set('SOMEPROP','FOO');
+ $comp->__set('ANOTHERPROP','FOO');
+ $comp->__set('THIRDPROP','FOO');
+ foreach ($prop4s as $prop4) {
+ $comp->add('PROP4', 'FOO '.$prop4);
+ }
+ $comp->__set('PROPNUMBERFIVE', 'FOO');
+ $comp->__set('PROPNUMBERSIX', 'FOO');
+ $comp->__set('PROPNUMBERSEVEN', 'FOO');
+ $comp->__set('PROPNUMBEREIGHT', 'FOO');
+ $comp->__set('PROPNUMBERNINE', 'FOO');
+ $comp->__set('PROPNUMBERTEN', 'FOO');
+ $comp->__set('VERSION','2.0');
+ $comp->__set('UID', 'FOO');
+
+ $str = $comp->serialize();
+
+ $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str);
+
+ }
+
+ function testInstantiateWithChildren() {
+
+ $comp = new VCard(array(
+ 'ORG' => array('Acme Inc.', 'Section 9'),
+ 'FN' => 'Finn The Human',
+ ));
+
+ $this->assertEquals(array('Acme Inc.', 'Section 9'), $comp->ORG->getParts());
+ $this->assertEquals('Finn The Human', $comp->FN->getValue());
+
+ }
+
+ function testInstantiateSubComponent() {
+
+ $comp = new VCalendar();
+ $event = $comp->createComponent('VEVENT', array(
+ $comp->createProperty('UID', '12345'),
+ ));
+ $comp->add($event);
+
+ $this->assertEquals('12345', $comp->VEVENT->UID->getValue());
+
+ }
+
+ function testRemoveByName() {
+
+ $comp = new VCalendar(array(), false);
+ $comp->add('prop1','val1');
+ $comp->add('prop2','val2');
+ $comp->add('prop2','val2');
+
+ $comp->remove('prop2');
+ $this->assertFalse(isset($comp->prop2));
+ $this->assertTrue(isset($comp->prop1));
+
+ }
+
+ function testRemoveByObj() {
+
+ $comp = new VCalendar(array(), false);
+ $comp->add('prop1','val1');
+ $prop = $comp->add('prop2','val2');
+
+ $comp->remove($prop);
+ $this->assertFalse(isset($comp->prop2));
+ $this->assertTrue(isset($comp->prop1));
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testRemoveNotFound() {
+
+ $comp = new VCalendar(array(), false);
+ $prop = $comp->createProperty('A','B');
+ $comp->remove($prop);
+
+ }
+
+ /**
+ * @dataProvider ruleData
+ */
+ function testValidateRules($componentList, $errorCount) {
+
+ $vcard = new Component\VCard();
+
+ $component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
+ foreach($componentList as $v) {
+ $component->add($v,'Hello.');
+ }
+
+ $this->assertEquals($errorCount, count($component->validate()));
+
+ }
+
+ function testValidateRepair() {
+
+ $vcard = new Component\VCard();
+
+ $component = new FakeComponent($vcard,'Hi', array(), $defaults = false );
+ $component->validate(Component::REPAIR);
+ $this->assertEquals('yow', $component->BAR->getValue());
+
+ }
+
+ function ruleData() {
+
+ return array(
+
+ array(array(), 2),
+ array(array('FOO'), 3),
+ array(array('BAR'), 1),
+ array(array('BAZ'), 1),
+ array(array('BAR','BAZ'), 0),
+ array(array('BAR','BAZ','ZIM',), 0),
+ array(array('BAR','BAZ','ZIM','GIR'), 0),
+ array(array('BAR','BAZ','ZIM','GIR','GIR'), 1),
+
+ );
+
+ }
+
+}
+
+class FakeComponent extends Component {
+
+ public function getValidationRules() {
+
+ return array(
+ 'FOO' => '0',
+ 'BAR' => '1',
+ 'BAZ' => '+',
+ 'ZIM' => '*',
+ 'GIR' => '?',
+ );
+
+ }
+
+ public function getDefaults() {
+
+ return array(
+ 'BAR' => 'yow',
+ );
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php b/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php
@@ -0,0 +1,417 @@
+assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true));
+ $this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D',true));
+ $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true));
+ $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true));
+ $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true));
+ $this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S',true));
+ $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S'));
+
+ }
+
+ function testParseICalendarDurationDateInterval() {
+
+ $expected = new DateInterval('P7D');
+ $this->assertEquals($expected, DateTimeParser::parseDuration('P1W'));
+ $this->assertEquals($expected, DateTimeParser::parse('P1W'));
+
+ $expected = new DateInterval('PT3M');
+ $expected->invert = true;
+ $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M'));
+
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ function testParseICalendarDurationFail() {
+
+ DateTimeParser::parseDuration('P1X',true);
+
+ }
+
+ function testParseICalendarDateTime() {
+
+ $dateTime = DateTimeParser::parseDateTime('20100316T141405');
+
+ $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
+
+ $this->assertEquals($compare, $dateTime);
+
+ }
+
+ /**
+ * @depends testParseICalendarDateTime
+ * @expectedException LogicException
+ */
+ function testParseICalendarDateTimeBadFormat() {
+
+ $dateTime = DateTimeParser::parseDateTime('20100316T141405 ');
+
+ }
+
+ /**
+ * @depends testParseICalendarDateTime
+ */
+ function testParseICalendarDateTimeUTC() {
+
+ $dateTime = DateTimeParser::parseDateTime('20100316T141405Z');
+
+ $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC'));
+ $this->assertEquals($compare, $dateTime);
+
+ }
+
+ /**
+ * @depends testParseICalendarDateTime
+ */
+ function testParseICalendarDateTimeUTC2() {
+
+ $dateTime = DateTimeParser::parseDateTime('20101211T160000Z');
+
+ $compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC'));
+ $this->assertEquals($compare, $dateTime);
+
+ }
+
+ /**
+ * @depends testParseICalendarDateTime
+ */
+ function testParseICalendarDateTimeCustomTimeZone() {
+
+ $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam'));
+
+ $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('Europe/Amsterdam'));
+ $this->assertEquals($compare, $dateTime);
+
+ }
+
+ function testParseICalendarDate() {
+
+ $dateTime = DateTimeParser::parseDate('20100316');
+
+ $expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC'));
+
+ $this->assertEquals($expected, $dateTime);
+
+ $dateTime = DateTimeParser::parse('20100316');
+ $this->assertEquals($expected, $dateTime);
+
+ }
+
+ /**
+ * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events
+ */
+ function testParseICalendarDateGreaterThan4000() {
+
+ $dateTime = DateTimeParser::parseDate('45001231');
+
+ $expected = new DateTime('4500-12-31 00:00:00',new DateTimeZone('UTC'));
+
+ $this->assertEquals($expected, $dateTime);
+
+ $dateTime = DateTimeParser::parse('45001231');
+ $this->assertEquals($expected, $dateTime);
+
+ }
+
+ /**
+ * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events
+ */
+ function testParseICalendarDateTimeGreaterThan4000() {
+
+ $dateTime = DateTimeParser::parseDateTime('45001231T235959');
+
+ $expected = new DateTime('4500-12-31 23:59:59',new DateTimeZone('UTC'));
+
+ $this->assertEquals($expected, $dateTime);
+
+ $dateTime = DateTimeParser::parse('45001231T235959');
+ $this->assertEquals($expected, $dateTime);
+
+ }
+
+ /**
+ * @depends testParseICalendarDate
+ * @expectedException LogicException
+ */
+ function testParseICalendarDateBadFormat() {
+
+ $dateTime = DateTimeParser::parseDate('20100316T141405');
+
+ }
+
+ /**
+ * @dataProvider vcardDates
+ */
+ function testVCardDate($input, $output) {
+
+ $this->assertEquals(
+ $output,
+ DateTimeParser::parseVCardDateTime($input)
+ );
+
+ }
+
+ /**
+ * @dataProvider vcardDates
+ * @expectedException \InvalidArgumentException
+ */
+ function testBadVCardDate() {
+
+ DateTimeParser::parseVCardDateTime('1985---01');
+
+ }
+
+ /**
+ * @dataProvider vcardDates
+ * @expectedException \InvalidArgumentException
+ */
+ function testBadVCardTime() {
+
+ DateTimeParser::parseVCardTime('23:12:166');
+
+ }
+
+ function vcardDates() {
+
+ return array(
+ array(
+ "19961022T140000",
+ array(
+ "year" => 1996,
+ "month" => 10,
+ "date" => 22,
+ "hour" => 14,
+ "minute" => 00,
+ "second" => 00,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "--1022T1400",
+ array(
+ "year" => null,
+ "month" => 10,
+ "date" => 22,
+ "hour" => 14,
+ "minute" => 00,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "---22T14",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => 22,
+ "hour" => 14,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "19850412",
+ array(
+ "year" => 1985,
+ "month" => 4,
+ "date" => 12,
+ "hour" => null,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "1985-04",
+ array(
+ "year" => 1985,
+ "month" => 04,
+ "date" => null,
+ "hour" => null,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "1985",
+ array(
+ "year" => 1985,
+ "month" => null,
+ "date" => null,
+ "hour" => null,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "--0412",
+ array(
+ "year" => null,
+ "month" => 4,
+ "date" => 12,
+ "hour" => null,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "---12",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => 12,
+ "hour" => null,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T102200",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => 10,
+ "minute" => 22,
+ "second" => 0,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T1022",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => 10,
+ "minute" => 22,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T10",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => 10,
+ "minute" => null,
+ "second" => null,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T-2200",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => null,
+ "minute" => 22,
+ "second" => 00,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T--00",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => null,
+ "minute" => null,
+ "second" => 00,
+ "timezone" => null
+ ),
+ ),
+ array(
+ "T102200Z",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => 10,
+ "minute" => 22,
+ "second" => 00,
+ "timezone" => 'Z'
+ ),
+ ),
+ array(
+ "T102200-0800",
+ array(
+ "year" => null,
+ "month" => null,
+ "date" => null,
+ "hour" => 10,
+ "minute" => 22,
+ "second" => 00,
+ "timezone" => '-0800'
+ ),
+ ),
+
+ // extended format
+ array(
+ "2012-11-29T15:10:53Z",
+ array(
+ "year" => 2012,
+ "month" => 11,
+ "date" => 29,
+ "hour" => 15,
+ "minute" => 10,
+ "second" => 53,
+ "timezone" => 'Z'
+ ),
+ ),
+
+ // with milliseconds
+ array(
+ "20121129T151053.123Z",
+ array(
+ "year" => 2012,
+ "month" => 11,
+ "date" => 29,
+ "hour" => 15,
+ "minute" => 10,
+ "second" => 53,
+ "timezone" => 'Z'
+ ),
+ ),
+
+ // extended format with milliseconds
+ array(
+ "2012-11-29T15:10:53.123Z",
+ array(
+ "year" => 2012,
+ "month" => 11,
+ "date" => 29,
+ "hour" => 15,
+ "minute" => 10,
+ "second" => 53,
+ "timezone" => 'Z'
+ ),
+ ),
+
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/DocumentTest.php b/vendor/sabre/vobject/tests/VObject/DocumentTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/DocumentTest.php
@@ -0,0 +1,70 @@
+assertEquals(Document::UNKNOWN, $doc->getDocumentType());
+
+ }
+
+ function testConstruct() {
+
+ $doc = new MockDocument('VLIST');
+ $this->assertEquals('VLIST', $doc->name);
+
+ }
+
+ function testCreateComponent() {
+
+ $vcal = new Component\VCalendar(array(), false);
+
+ $event = $vcal->createComponent('VEVENT');
+
+ $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
+ $vcal->add($event);
+
+ $prop = $vcal->createProperty('X-PROP','1234256',array('X-PARAM' => '3'));
+ $this->assertInstanceOf('Sabre\VObject\Property', $prop);
+
+ $event->add($prop);
+
+ unset(
+ $event->DTSTAMP,
+ $event->UID
+ );
+
+ $out = $vcal->serialize();
+ $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out);
+
+ }
+
+ function testCreate() {
+
+ $vcal = new Component\VCalendar(array(), false);
+
+ $event = $vcal->create('VEVENT');
+ $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event);
+
+ $event = $vcal->create('CALSCALE');
+ $this->assertInstanceOf('Sabre\VObject\Property\Text', $event);
+
+ }
+
+ function testGetClassNameForPropertyValue() {
+
+ $vcal = new Component\VCalendar(array(), false);
+ $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT'));
+ $this->assertNull($vcal->getClassNameForPropertyValue('FOO'));
+
+ }
+
+}
+
+
+class MockDocument extends Document {
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ElementListTest.php b/vendor/sabre/vobject/tests/VObject/ElementListTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ElementListTest.php
@@ -0,0 +1,33 @@
+createComponent('VEVENT');
+
+ $elems = array(
+ $sub,
+ clone $sub,
+ clone $sub
+ );
+
+ $elemList = new ElementList($elems);
+
+ $count = 0;
+ foreach($elemList as $key=>$subcomponent) {
+
+ $count++;
+ $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent);
+
+ }
+ $this->assertEquals(3,$count);
+ $this->assertEquals(2,$key);
+
+ }
+
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/EmClientTest.php b/vendor/sabre/vobject/tests/VObject/EmClientTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/EmClientTest.php
@@ -0,0 +1,55 @@
+VEVENT->DTSTART->getDateTime();
+ $this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt);
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php b/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php
@@ -0,0 +1,69 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
+ $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30);
+ $vcard = $vcard->serialize();
+
+ $converted = Reader::read($vcard);
+ $converted->validate();
+
+ $this->assertTrue(isset($converted->EMAIL['X-INTERN']));
+
+ $version = Version::VERSION;
+
+ $expected = <<assertEquals($expected, str_replace("\r","", $vcard));
+
+ }
+
+ function testVCard21Parameter() {
+
+ $vcard = new Component\VCard(array(), false);
+ $vcard->VERSION = '2.1';
+ $vcard->PHOTO = 'random_stuff';
+ $vcard->PHOTO->add(null,'BASE64');
+ $vcard->UID = 'foo-bar';
+
+ $result = $vcard->serialize();
+ $expected = array(
+ "BEGIN:VCARD",
+ "VERSION:2.1",
+ "PHOTO;BASE64:" . base64_encode('random_stuff'),
+ "UID:foo-bar",
+ "END:VCARD",
+ "",
+ );
+
+ $this->assertEquals(implode("\r\n", $expected), $result);
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php b/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php
@@ -0,0 +1,31 @@
+assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue());
+
+ }
+
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php b/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php
@@ -0,0 +1,394 @@
+getResult();
+
+ $expected = (array)$expected;
+
+ $freebusy = $result->VFREEBUSY->select('FREEBUSY');
+
+ foreach($freebusy as $fb) {
+
+ $this->assertContains((string)$fb, $expected, "$fb did not appear in our list of expected freebusy strings. This is concerning!");
+
+ $k = array_search((string)$fb, $expected);
+ unset($expected[$k]);
+
+ }
+ $this->assertTrue(
+ count($expected) === 0,
+ 'There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize()
+ );
+
+ }
+
+ function testGeneratorBaseObject() {
+
+ $obj = new Component\VCalendar();
+ $obj->METHOD = 'PUBLISH';
+
+ $gen = new FreeBusyGenerator();
+ $gen->setObjects(array());
+ $gen->setBaseObject($obj);
+
+ $result = $gen->getResult();
+
+ $this->assertEquals('PUBLISH', $result->METHOD->getValue());
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testInvalidArg() {
+
+ $gen = new FreeBusyGenerator(
+ new \DateTime('2012-01-01'),
+ new \DateTime('2012-12-31'),
+ new \StdClass()
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php b/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php
@@ -0,0 +1,31 @@
+assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php b/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php
@@ -0,0 +1,31 @@
+VEVENT->ATTACH;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop);
+ $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue());
+
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php
@@ -0,0 +1,1130 @@
+ 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testRecurringReply() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testRecurringAllDay() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testNoChange() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testNoChangeForceSend() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testNoRelevantAttendee() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * In this test, an event exists in an attendees calendar. The event
+ * is recurring, and the attendee deletes 1 instance of the event.
+ * This instance shows up in EXDATE
+ *
+ * This should automatically generate a DECLINED message for that
+ * specific instance.
+ */
+ function testCreateReplyByException() {
+
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => null,
+ 'recipient' => 'mailto:organizer@example.org',
+ 'recipientName' => null,
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * This test is identical to the last, but now we're working with
+ * timezones.
+ *
+ * @depends testCreateReplyByException
+ */
+ function testCreateReplyByExceptionTz() {
+
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => null,
+ 'recipient' => 'mailto:organizer@example.org',
+ 'recipientName' => null,
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * @depends testCreateReplyByException
+ */
+ function testCreateReplyByExceptionAllDay() {
+
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => null,
+ 'recipient' => 'mailto:organizer@example.org',
+ 'recipientName' => null,
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testDeclined() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testDeclinedCancelledEvent() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * In this test, a new exception is created by an attendee as well.
+ *
+ * Except in this case, there was already an overridden event, and the
+ * overridden event was marked as cancelled by the attendee.
+ *
+ * For any other attendence status, the new status would have been
+ * declined, but for this, no message should we sent.
+ */
+ function testDontCreateReplyWhenEventWasDeclined() {
+
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testScheduleAgentOnOrganizer() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ function testAcceptedAllDay() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * This function tests an attendee updating their status to an event where
+ * they don't have the master event of.
+ *
+ * This is possible in cases an organizer created a recurring event, and
+ * invited an attendee for one instance of the event.
+ */
+ function testReplyNoMasterEvent() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+
+ /**
+ * A party crasher is an attendee that accepted an event, but was not in
+ * any original invite.
+ *
+ * @depends testAccepted
+ */
+ function testPartyCrasher() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected);
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php
@@ -0,0 +1,340 @@
+ 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testOrganizerDeleteWithDuration() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testAttendeeDeleteWithDtend() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
+
+
+ }
+
+ function testAttendeeDeleteWithDuration() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REPLY',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:one@example.org',
+ 'senderName' => 'One',
+ 'recipient' => 'mailto:strunk@example.org',
+ 'recipientName' => 'Strunk',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
+
+
+ }
+
+ function testAttendeeDeleteCancelledEvent() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org');
+
+
+ }
+
+ function testNoCalendar() {
+
+ $this->parse(null, null, array(), 'mailto:one@example.org');
+
+ }
+
+ function testVTodo() {
+
+ $oldMessage = <<parse($oldMessage, null, array(), 'mailto:one@example.org');
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php
@@ -0,0 +1,528 @@
+parse($message);
+
+ }
+
+ function testVTODO() {
+
+ $message = <<parse($message);
+
+ }
+
+ function testSimpleInvite() {
+
+ $message = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:white@example.org',
+ 'recipientName' => 'White',
+ 'message' => $expectedMessage,
+ ),
+ );
+
+ $result = $this->parse($message, $expected);
+
+ }
+
+ /**
+ * @expectedException \Sabre\VObject\ITip\ITipException
+ */
+ function testBrokenEventUIDMisMatch() {
+
+ $message = <<parse($message, array());
+
+ }
+ /**
+ * @expectedException \Sabre\VObject\ITip\ITipException
+ */
+ function testBrokenEventOrganizerMisMatch() {
+
+ $message = <<parse($message, array());
+
+ }
+
+ function testRecurrenceInvite() {
+
+ $message = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:three@example.org',
+ 'recipientName' => 'Three',
+ 'message' => <<parse($message, $expected);
+
+ }
+
+ function testRecurrenceInvite2() {
+
+ // This method tests a nearly identical path, but in this case the
+ // master event does not have an EXDATE.
+ $message = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:three@example.org',
+ 'recipientName' => 'Three',
+ 'message' => <<parse($message, $expected);
+
+ }
+
+ function testScheduleAgentClient() {
+
+ $message = <<parse($message, $expected);
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ITip\ITipException
+ */
+ function testMultipleUID() {
+
+ $message = <<parse($message, array());
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException
+ *
+ */
+ function testChangingOrganizers() {
+
+ $message = <<parse($message, array());
+
+ }
+ function testNoOrganizerHasAttendee() {
+
+ $message = <<parse($message, array());
+
+ }
+
+ function parse($message, $expected = array()) {
+
+ $broker = new Broker();
+ $result = $broker->parseEvent($message, 'mailto:strunk@example.org');
+
+ $this->assertEquals(count($expected), count($result));
+
+ foreach($expected as $index=>$ex) {
+
+ $message = $result[$index];
+
+ foreach($ex as $key=>$val) {
+
+ if ($key==='message') {
+ $this->assertEquals(
+ str_replace("\n", "\r\n", $val),
+ rtrim($message->message->serialize(), "\r\n")
+ );
+ } else {
+ $this->assertEquals($val, $message->$key);
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php
@@ -0,0 +1,168 @@
+process($itip, null, $expected);
+
+ }
+
+ function testRequestUpdate() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testCancel() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testCancelNoExistingEvent() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testUnsupportedComponent() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testUnsupportedMethod() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php
@@ -0,0 +1,496 @@
+process($itip, $old, $expected);
+
+ }
+
+ function testReplyAccept() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyRequestStatus() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+
+ function testReplyPartyCrasher() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyNewException() {
+
+ // This is a reply to 1 instance of a recurring event. This should
+ // automatically create an exception.
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyNewExceptionTz() {
+
+ // This is a reply to 1 instance of a recurring event. This should
+ // automatically create an exception.
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyPartyCrashCreateExcepton() {
+
+ // IN this test there's a recurring event that has an exception. The
+ // exception is missing the attendee.
+ //
+ // The attendee party crashes the instance, so it should show up in the
+ // resulting object.
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyNewExceptionNoMasterEvent() {
+
+ /**
+ * This iTip message would normally create a new exception, but the
+ * server is not able to create this new instance, because there's no
+ * master event to clone from.
+ *
+ * This test checks if the message is ignored.
+ */
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ /**
+ * @depends testReplyAccept
+ */
+ function testReplyAcceptUpdateRSVP() {
+
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+ function testReplyNewExceptionFirstOccurence() {
+
+ // This is a reply to 1 instance of a recurring event. This should
+ // automatically create an exception.
+ $itip = <<process($itip, $old, $expected);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php
@@ -0,0 +1,103 @@
+parseEvent($newMessage, $currentUser, $oldMessage);
+
+ $this->assertEquals(count($expected), count($result));
+
+ foreach($expected as $index=>$ex) {
+
+ $message = $result[$index];
+
+ foreach($ex as $key=>$val) {
+
+ if ($key==='message') {
+ $this->assertVObjEquals(
+ $val,
+ $message->message->serialize()
+ );
+ } else {
+ $this->assertEquals($val, $message->$key);
+ }
+
+ }
+
+ }
+
+ }
+
+ function process($input, $existingObject = null, $expected = false) {
+
+ $version = \Sabre\VObject\Version::VERSION;
+
+ $vcal = Reader::read($input);
+
+ foreach($vcal->getComponents() as $mainComponent) {
+ break;
+ }
+
+ $message = new Message();
+ $message->message = $vcal;
+ $message->method = isset($vcal->METHOD)?$vcal->METHOD->getValue():null;
+ $message->component = $mainComponent->name;
+ $message->uid = $mainComponent->uid->getValue();
+ $message->sequence = isset($vcal->VEVENT[0])?(string)$vcal->VEVENT[0]->SEQUENCE:null;
+
+ if ($message->method === 'REPLY') {
+
+ $message->sender = $mainComponent->ATTENDEE->getValue();
+ $message->senderName = isset($mainComponent->ATTENDEE['CN'])?$mainComponent->ATTENDEE['CN']->getValue():null;
+ $message->recipient = $mainComponent->ORGANIZER->getValue();
+ $message->recipientName = isset($mainComponent->ORGANIZER['CN'])?$mainComponent->ORGANIZER['CN']:null;
+
+ }
+
+ $broker = new Broker();
+
+ if (is_string($existingObject)) {
+ $existingObject = str_replace(
+ '%foo%',
+ "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN",
+ $existingObject
+ );
+ $existingObject = Reader::read($existingObject);
+ }
+
+ $result = $broker->processMessage($message, $existingObject);
+
+ if (is_string($expected)) {
+ $expected = str_replace(
+ '%foo%',
+ "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN",
+ $expected
+ );
+ $expected = str_replace("\n", "\r\n", $expected);
+
+ }
+ if ($result instanceof \Sabre\VObject\Component\VCalendar) {
+ $result = $result->serialize();
+ $result = rtrim($result,"\r\n");
+ }
+
+ $this->assertEquals(
+ $expected,
+ $result
+ );
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerTimezoneInParseEventInfoWithoutMasterTest.php
@@ -0,0 +1,77 @@
+setAccessible(true);
+ $data = $reflectionMethod->invoke($broker, $calendar);
+ $this->assertInstanceOf('DateTimeZone', $data['timezone']);
+ $this->assertEquals($data['timezone']->getName(), 'Europe/Minsk');
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php b/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php
@@ -0,0 +1,841 @@
+ 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => true,
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'significantChange' => false,
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:three@example.org',
+ 'recipientName' => 'Three',
+ 'significantChange' => true,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteChangeFromNonSchedulingToSchedulingObject() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteChangeFromSchedulingToNonSchedulingObject() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testNoAttendees() {
+
+ $oldMessage = <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testRemoveInstance() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ /**
+ * This test is identical to the first test, except this time we change the
+ * DURATION property.
+ *
+ * This should ensure that the message is significant for every attendee,
+ */
+ function testInviteChangeSignificantChange() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => true,
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'significantChange' => true,
+ 'message' => << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:three@example.org',
+ 'recipientName' => 'Three',
+ 'significantChange' => true,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteNoChange() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => false,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteNoChangeForceSend() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => true,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteRemoveAttendees() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => true,
+ 'message' => << 'foobar',
+ 'method' => 'CANCEL',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:two@example.org',
+ 'recipientName' => 'Two',
+ 'significantChange' => true,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+
+ function testInviteChangeExdateOrder() {
+
+ $oldMessage = << 'foobar',
+ 'method' => 'REQUEST',
+ 'component' => 'VEVENT',
+ 'sender' => 'mailto:strunk@example.org',
+ 'senderName' => 'Strunk',
+ 'recipient' => 'mailto:one@example.org',
+ 'recipientName' => 'One',
+ 'significantChange' => false,
+ 'message' => <<parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org');
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php b/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php
@@ -0,0 +1,2653 @@
+ '20140813T153116Z-12176-1000-1065-6@johnny-lubuntu',
+ 'method' => 'REQUEST',
+ 'sender' => 'mailto:martin@fruux.com',
+ 'senderName' => null,
+ 'recipient' => 'mailto:dominik@fruux.com',
+ 'recipientName' => null,
+ 'message' => $expectedICS,
+ )
+ );
+ $this->parse(null, $ics, $expected, 'mailto:martin@fruux.com');
+
+ }
+
+ /**
+ * This is an event originally from evolution, then parsed by sabredav and
+ * again mangled by iCal. This triggered a few bugs related to email
+ * address scheme casing.
+ */
+ public function testAttendeeModify() {
+
+ $old = <<parse($old, $new, array(), 'mailto:a1@example.org');
+
+
+ }
+
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php b/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php
@@ -0,0 +1,32 @@
+assertFalse($message->getScheduleStatus());
+
+ }
+
+ public function testScheduleStatus() {
+
+ $message = new Message();
+ $message->scheduleStatus = '1.2;Delivered';
+
+ $this->assertEquals('1.2', $message->getScheduleStatus());
+
+ }
+
+ public function testUnexpectedScheduleStatus() {
+
+ $message = new Message();
+ $message->scheduleStatus = '9.9.9';
+
+ $this->assertEquals('9.9.9', $message->getScheduleStatus());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue153Test.php b/vendor/sabre/vobject/tests/VObject/Issue153Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue153Test.php
@@ -0,0 +1,14 @@
+assertEquals('Test Benutzer', (string)$obj->fn);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue26Test.php b/vendor/sabre/vobject/tests/VObject/Issue26Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue26Test.php
@@ -0,0 +1,36 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $it = new Recur\EventIterator($vcal, 'bae5d57a98');
+ iterator_to_array($it);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php b/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php
@@ -0,0 +1,39 @@
+assertInstanceOf('Sabre\\VObject\\Recur\EventIterator', $it);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue40Test.php b/vendor/sabre/vobject/tests/VObject/Issue40Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue40Test.php
@@ -0,0 +1,30 @@
+add('N', array('van der Harten', array('Rene','J.'), "", 'Sir','R.D.O.N.'), array('SORT-AS' => array('Harten','Rene')));
+
+ $expected = implode("\r\n", array(
+ "BEGIN:VCARD",
+ "VERSION:3.0",
+ "PRODID:-//Sabre//Sabre VObject " . Version::VERSION . '//EN',
+ "N;SORT-AS=Harten,Rene:van der Harten;Rene,J.;;Sir;R.D.O.N.",
+ "END:VCARD",
+ ""
+ ));
+
+ $this->assertEquals($expected, $card->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue64Test.php b/vendor/sabre/vobject/tests/VObject/Issue64Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue64Test.php
@@ -0,0 +1,19 @@
+convert(\Sabre\VObject\Document::VCARD30);
+ $vcard = $vcard->serialize();
+
+ $converted = Reader::read($vcard);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Issue96Test.php b/vendor/sabre/vobject/tests/VObject/Issue96Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Issue96Test.php
@@ -0,0 +1,24 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
+ $this->assertEquals("http://www.example.org", $vcard->url->getValue());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/JCalTest.php b/vendor/sabre/vobject/tests/VObject/JCalTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/JCalTest.php
@@ -0,0 +1,150 @@
+add('VEVENT', array(
+ "UID" => "foo",
+ "DTSTART" => new \DateTime("2013-05-26 18:10:00Z"),
+ "DURATION" => "P1D",
+ "CATEGORIES" => array('home', 'testing'),
+ "CREATED" => new \DateTime("2013-05-26 18:10:00Z"),
+
+ "ATTENDEE" => "mailto:armin@example.org",
+ "GEO" => array(51.96668, 7.61876),
+ "SEQUENCE" => 5,
+ "FREEBUSY" => array("20130526T210213Z/PT1H", "20130626T120000Z/20130626T130000Z"),
+ "URL" => "http://example.org/",
+ "TZOFFSETFROM" => "+05:00",
+ "RRULE" => array('FREQ' => 'WEEKLY', 'BYDAY' => array('MO','TU')),
+ ));
+
+ // Modifying DTSTART to be a date-only.
+ $event->dtstart['VALUE'] = 'DATE';
+ $event->add("X-BOOL", true, array('VALUE' => 'BOOLEAN'));
+ $event->add("X-TIME", "08:00:00", array('VALUE' => 'TIME'));
+ $event->add("ATTACH", "attachment", array('VALUE' => 'BINARY'));
+ $event->add("ATTENDEE", "mailto:dominik@example.org", array("CN" => "Dominik", "PARTSTAT" => "DECLINED"));
+
+ $event->add('REQUEST-STATUS', array("2.0", "Success"));
+ $event->add('REQUEST-STATUS', array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"));
+
+ $event->add('DTEND', '20150108T133000');
+
+ $expected = array(
+ "vcalendar",
+ array(
+ array(
+ "version",
+ new \StdClass(),
+ "text",
+ "2.0"
+ ),
+ array(
+ "prodid",
+ new \StdClass(),
+ "text",
+ "-//Sabre//Sabre VObject " . Version::VERSION . "//EN",
+ ),
+ array(
+ "calscale",
+ new \StdClass(),
+ "text",
+ "GREGORIAN"
+ ),
+ ),
+ array(
+ array("vevent",
+ array(
+ array(
+ "uid", new \StdClass(), "text", "foo",
+ ),
+ array(
+ "dtstart", new \StdClass(), "date", "2013-05-26",
+ ),
+ array(
+ "duration", new \StdClass(), "duration", "P1D",
+ ),
+ array(
+ "categories", new \StdClass(), "text", "home", "testing",
+ ),
+ array(
+ "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z",
+ ),
+
+ array(
+ "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org",
+ ),
+ array(
+ "geo", new \StdClass(), "float", array(51.96668, 7.61876),
+ ),
+ array(
+ "sequence", new \StdClass(), "integer", 5
+ ),
+ array(
+ "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"),
+ ),
+ array(
+ "url", new \StdClass(), "uri", "http://example.org/",
+ ),
+ array(
+ "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00",
+ ),
+ array(
+ "rrule", new \StdClass(), "recur", array(
+ 'freq' => 'WEEKLY',
+ 'byday' => array('MO', 'TU'),
+ ),
+ ),
+ array(
+ "x-bool", new \StdClass(), "boolean", true
+ ),
+ array(
+ "x-time", new \StdClass(), "time", "08:00:00",
+ ),
+ array(
+ "attach", new \StdClass(), "binary", base64_encode('attachment')
+ ),
+ array(
+ "attendee",
+ (object)array(
+ "cn" => "Dominik",
+ "partstat" => "DECLINED",
+ ),
+ "cal-address",
+ "mailto:dominik@example.org"
+ ),
+ array(
+ "request-status",
+ new \StdClass(),
+ "text",
+ array("2.0", "Success"),
+ ),
+ array(
+ "request-status",
+ new \StdClass(),
+ "text",
+ array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"),
+ ),
+ array(
+ 'dtend',
+ new \StdClass(),
+ "date-time",
+ "2015-01-08T13:30:00",
+ ),
+ ),
+ array(),
+ )
+ ),
+ );
+
+ $this->assertEquals($expected, $cal->jsonSerialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/JCardTest.php b/vendor/sabre/vobject/tests/VObject/JCardTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/JCardTest.php
@@ -0,0 +1,195 @@
+ "4.0",
+ "UID" => "foo",
+ "BDAY" => "19850407",
+ "REV" => "19951031T222710Z",
+ "LANG" => "nl",
+ "N" => array("Last", "First", "Middle", "", ""),
+ "item1.TEL" => "+1 555 123456",
+ "item1.X-AB-LABEL" => "Walkie Talkie",
+ "ADR" => array(
+ "",
+ "",
+ array("My Street", "Left Side", "Second Shack"),
+ "Hometown",
+ "PA",
+ "18252",
+ "U.S.A",
+ ),
+ ));
+
+ $card->add('BDAY', '1979-12-25', array('VALUE' => 'DATE', 'X-PARAM' => array(1,2)));
+ $card->add('BDAY', '1979-12-25T02:00:00', array('VALUE' => 'DATE-TIME'));
+
+
+ $card->add('X-TRUNCATED', '--1225', array('VALUE' => 'DATE'));
+ $card->add('X-TIME-LOCAL', '123000', array('VALUE' => 'TIME'));
+ $card->add('X-TIME-UTC', '12:30:00Z', array('VALUE' => 'TIME'));
+ $card->add('X-TIME-OFFSET', '12:30:00-08:00', array('VALUE' => 'TIME'));
+ $card->add('X-TIME-REDUCED', '23', array('VALUE' => 'TIME'));
+ $card->add('X-TIME-TRUNCATED', '--30', array('VALUE' => 'TIME'));
+
+ $card->add('X-KARMA-POINTS', '42', array('VALUE' => 'INTEGER'));
+ $card->add('X-GRADE', '1.3', array('VALUE' => 'FLOAT'));
+
+ $card->add('TZ', '-05:00', array('VALUE' => 'UTC-OFFSET'));
+
+ $expected = array(
+ "vcard",
+ array(
+ array(
+ "version",
+ new \StdClass(),
+ "text",
+ "4.0"
+ ),
+ array(
+ "prodid",
+ new \StdClass(),
+ "text",
+ "-//Sabre//Sabre VObject " . Version::VERSION . "//EN",
+ ),
+ array(
+ "uid",
+ new \StdClass(),
+ "text",
+ "foo",
+ ),
+ array(
+ "bday",
+ new \StdClass(),
+ "date-and-or-time",
+ "1985-04-07",
+ ),
+ array(
+ "rev",
+ new \StdClass(),
+ "timestamp",
+ "1995-10-31T22:27:10Z",
+ ),
+ array(
+ "lang",
+ new \StdClass(),
+ "language-tag",
+ "nl",
+ ),
+ array(
+ "n",
+ new \StdClass(),
+ "text",
+ array("Last", "First", "Middle", "", ""),
+ ),
+ array(
+ "tel",
+ (object)array(
+ "group" => "item1",
+ ),
+ "text",
+ "+1 555 123456",
+ ),
+ array(
+ "x-ab-label",
+ (object)array(
+ "group" => "item1",
+ ),
+ "unknown",
+ "Walkie Talkie",
+ ),
+ array(
+ "adr",
+ new \StdClass(),
+ "text",
+ array(
+ "",
+ "",
+ array("My Street", "Left Side", "Second Shack"),
+ "Hometown",
+ "PA",
+ "18252",
+ "U.S.A",
+ ),
+ ),
+ array(
+ "bday",
+ (object)array(
+ 'x-param' => array(1,2),
+ ),
+ "date",
+ "1979-12-25",
+ ),
+ array(
+ "bday",
+ new \StdClass(),
+ "date-time",
+ "1979-12-25T02:00:00",
+ ),
+ array(
+ "x-truncated",
+ new \StdClass(),
+ "date",
+ "--12-25",
+ ),
+ array(
+ "x-time-local",
+ new \StdClass(),
+ "time",
+ "12:30:00"
+ ),
+ array(
+ "x-time-utc",
+ new \StdClass(),
+ "time",
+ "12:30:00Z"
+ ),
+ array(
+ "x-time-offset",
+ new \StdClass(),
+ "time",
+ "12:30:00-08:00"
+ ),
+ array(
+ "x-time-reduced",
+ new \StdClass(),
+ "time",
+ "23"
+ ),
+ array(
+ "x-time-truncated",
+ new \StdClass(),
+ "time",
+ "--30"
+ ),
+ array(
+ "x-karma-points",
+ new \StdClass(),
+ "integer",
+ 42
+ ),
+ array(
+ "x-grade",
+ new \StdClass(),
+ "float",
+ 1.3
+ ),
+ array(
+ "tz",
+ new \StdClass(),
+ "utc-offset",
+ "-05:00",
+ ),
+ ),
+ );
+
+ $this->assertEquals($expected, $card->jsonSerialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php b/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php
@@ -0,0 +1,23 @@
+assertEquals($event, $obj->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ParameterTest.php b/vendor/sabre/vobject/tests/VObject/ParameterTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ParameterTest.php
@@ -0,0 +1,135 @@
+assertEquals('NAME',$param->name);
+ $this->assertEquals('value',$param->getValue());
+
+ }
+
+ function testSetupNameLess() {
+
+ $card = new Component\VCard();
+
+ $param = new Parameter($card, null,'URL');
+ $this->assertEquals('VALUE',$param->name);
+ $this->assertEquals('URL',$param->getValue());
+ $this->assertTrue($param->noName);
+
+ }
+
+ function testModify() {
+
+ $cal = new Component\VCalendar();
+
+ $param = new Parameter($cal, 'name', null);
+ $param->addValue(1);
+ $this->assertEquals(array(1), $param->getParts());
+
+ $param->setParts(array(1,2));
+ $this->assertEquals(array(1,2), $param->getParts());
+
+ $param->addValue(3);
+ $this->assertEquals(array(1,2,3), $param->getParts());
+
+ $param->setValue(4);
+ $param->addValue(5);
+ $this->assertEquals(array(4,5), $param->getParts());
+
+ }
+
+ function testCastToString() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name', 'value');
+ $this->assertEquals('value',$param->__toString());
+ $this->assertEquals('value',(string)$param);
+
+ }
+
+ function testCastNullToString() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name', null);
+ $this->assertEquals('',$param->__toString());
+ $this->assertEquals('',(string)$param);
+
+ }
+
+ function testSerialize() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name', 'value');
+ $this->assertEquals('NAME=value',$param->serialize());
+
+ }
+
+ function testSerializeEmpty() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name', null);
+ $this->assertEquals('NAME=',$param->serialize());
+
+ }
+
+ function testSerializeComplex() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name',array("val1", "val2;", "val3^", "val4\n", "val5\""));
+ $this->assertEquals('NAME=val1,"val2;","val3^^","val4^n","val5^\'"',$param->serialize());
+
+ }
+
+ /**
+ * iCal 7.0 (OSX 10.9) has major issues with the EMAIL property, when the
+ * value contains a plus sign, and it's not quoted.
+ *
+ * So we specifically added support for that.
+ */
+ function testSerializePlusSign() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'EMAIL',"user+something@example.org");
+ $this->assertEquals('EMAIL="user+something@example.org"',$param->serialize());
+
+ }
+
+ function testIterate() {
+
+ $cal = new Component\VCalendar();
+
+ $param = new Parameter($cal, 'name', array(1,2,3,4));
+ $result = array();
+
+ foreach($param as $value) {
+ $result[] = $value;
+ }
+
+ $this->assertEquals(array(1,2,3,4), $result);
+
+ }
+
+ function testSerializeColon() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name','va:lue');
+ $this->assertEquals('NAME="va:lue"',$param->serialize());
+
+ }
+
+ function testSerializeSemiColon() {
+
+ $cal = new Component\VCalendar();
+ $param = new Parameter($cal, 'name','va;lue');
+ $this->assertEquals('NAME="va;lue"',$param->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php b/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php
@@ -0,0 +1,395 @@
+ "item1",
+ ),
+ "text",
+ "+1 555 123456",
+ ),
+ array(
+ "x-ab-label",
+ (object)array(
+ "group" => "item1",
+ ),
+ "unknown",
+ "Walkie Talkie",
+ ),
+ array(
+ "adr",
+ new \StdClass(),
+ "text",
+ array(
+ "",
+ "",
+ array("My Street", "Left Side", "Second Shack"),
+ "Hometown",
+ "PA",
+ "18252",
+ "U.S.A",
+ ),
+ ),
+ array(
+ "bday",
+ (object)array(
+ 'x-param' => array(1,2),
+ ),
+ "date",
+ "1979-12-25",
+ ),
+ array(
+ "bday",
+ new \StdClass(),
+ "date-time",
+ "1979-12-25T02:00:00",
+ ),
+ array(
+ "x-truncated",
+ new \StdClass(),
+ "date",
+ "--12-25",
+ ),
+ array(
+ "x-time-local",
+ new \StdClass(),
+ "time",
+ "12:30:00"
+ ),
+ array(
+ "x-time-utc",
+ new \StdClass(),
+ "time",
+ "12:30:00Z"
+ ),
+ array(
+ "x-time-offset",
+ new \StdClass(),
+ "time",
+ "12:30:00-08:00"
+ ),
+ array(
+ "x-time-reduced",
+ new \StdClass(),
+ "time",
+ "23"
+ ),
+ array(
+ "x-time-truncated",
+ new \StdClass(),
+ "time",
+ "--30"
+ ),
+ array(
+ "x-karma-points",
+ new \StdClass(),
+ "integer",
+ 42
+ ),
+ array(
+ "x-grade",
+ new \StdClass(),
+ "float",
+ 1.3
+ ),
+ array(
+ "tz",
+ new \StdClass(),
+ "utc-offset",
+ "-05:00",
+ ),
+ ),
+ );
+
+ $parser = new Json(json_encode($input));
+ $vobj = $parser->parse();
+
+ $version = VObject\Version::VERSION;
+
+
+ $result = $vobj->serialize();
+ $expected = <<assertEquals($expected, str_replace("\r", "", $result));
+
+ $this->assertEquals(
+ $input,
+ $vobj->jsonSerialize()
+ );
+
+ }
+
+ function testRoundTripJCal() {
+
+ $input = array(
+ "vcalendar",
+ array(
+ array(
+ "version",
+ new \StdClass(),
+ "text",
+ "2.0"
+ ),
+ array(
+ "prodid",
+ new \StdClass(),
+ "text",
+ "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN",
+ ),
+ array(
+ "calscale",
+ new \StdClass(),
+ "text",
+ "GREGORIAN"
+ ),
+ ),
+ array(
+ array("vevent",
+ array(
+ array(
+ "uid", new \StdClass(), "text", "foo",
+ ),
+ array(
+ "dtstart", new \StdClass(), "date", "2013-05-26",
+ ),
+ array(
+ "duration", new \StdClass(), "duration", "P1D",
+ ),
+ array(
+ "categories", new \StdClass(), "text", "home", "testing",
+ ),
+ array(
+ "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z",
+ ),
+ array(
+ "attach", new \StdClass(), "binary", base64_encode('attachment')
+ ),
+ array(
+ "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org",
+ ),
+ array(
+ "geo", new \StdClass(), "float", array(51.96668, 7.61876),
+ ),
+ array(
+ "sequence", new \StdClass(), "integer", 5
+ ),
+ array(
+ "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"),
+ ),
+ array(
+ "url", new \StdClass(), "uri", "http://example.org/",
+ ),
+ array(
+ "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00",
+ ),
+ array(
+ "rrule", new \StdClass(), "recur", array(
+ 'freq' => 'WEEKLY',
+ 'byday' => array('MO', 'TU'),
+ ),
+ ),
+ array(
+ "x-bool", new \StdClass(), "boolean", true
+ ),
+ array(
+ "x-time", new \StdClass(), "time", "08:00:00",
+ ),
+ array(
+ "attendee",
+ (object)array(
+ "cn" => "Dominik",
+ "partstat" => "DECLINED",
+ ),
+ "cal-address",
+ "mailto:dominik@example.org"
+ ),
+ array(
+ "request-status",
+ new \StdClass(),
+ "text",
+ array("2.0", "Success"),
+ ),
+ array(
+ "request-status",
+ new \StdClass(),
+ "text",
+ array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"),
+ ),
+ ),
+ array(
+ array("valarm",
+ array(
+ array(
+ "action", new \StdClass(), "text", "DISPLAY",
+ ),
+ ),
+ array(),
+ ),
+ ),
+ )
+ ),
+ );
+
+ $parser = new Json(json_encode($input));
+ $vobj = $parser->parse();
+ $result = $vobj->serialize();
+
+ $version = VObject\Version::VERSION;
+
+ $expected = <<assertEquals($expected, str_replace("\r", "", $result));
+
+ $this->assertEquals(
+ $input,
+ $vobj->jsonSerialize()
+ );
+
+ }
+
+ function testParseStreamArg() {
+
+ $input = array(
+ "vcard",
+ array(
+ array(
+ "FN", new \StdClass(), 'text', "foo",
+ ),
+ ),
+ );
+
+ $stream = fopen('php://memory','r+');
+ fwrite($stream, json_encode($input));
+ rewind($stream);
+
+ $result = VObject\Reader::readJson($stream,0);
+ $this->assertEquals('foo', $result->FN->getValue());
+
+ }
+
+ /**
+ * @expectedException \Sabre\VObject\ParseException
+ */
+ function testParseInvalidData() {
+
+ $json = new Json();
+ $input = array(
+ "vlist",
+ array(
+ array(
+ "FN", new \StdClass(), 'text', "foo",
+ ),
+ ),
+ );
+
+ $json->parse(json_encode($input), 0);
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php b/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php
@@ -0,0 +1,21 @@
+parse(fopen(__FILE__,'a'));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php b/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php
@@ -0,0 +1,108 @@
+assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCARD', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertEquals("Aachen", $this->getPropertyValue($result->label));
+
+ }
+
+ function testReadQuotedPrintableNewlineSoft() {
+
+ $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD";
+ $result = Reader::read($data);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCARD', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertEquals("Aachen", $this->getPropertyValue($result->label));
+
+ }
+
+ function testReadQuotedPrintableNewlineHard() {
+
+ $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD";
+ $result = Reader::read($data);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCARD', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->label));
+
+
+ }
+
+ function testReadQuotedPrintableCompatibilityMS() {
+
+ $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD";
+ $result = Reader::read($data, Reader::OPTION_FORGIVING);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCARD', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->label));
+
+ }
+
+ function testReadQuotesPrintableCompoundValues() {
+
+ $data = <<assertEquals(array(
+ '','','Münster Str. 1','Münster','','48143','Deutschland'
+ ), $result->ADR->getParts());
+
+
+ }
+
+ private function getPropertyValue(\Sabre\VObject\Property $property) {
+
+ return (string)$property;
+
+ /*
+ $param = $property['encoding'];
+ if ($param !== null) {
+ $encoding = strtoupper((string)$param);
+ if ($encoding === 'QUOTED-PRINTABLE') {
+ $value = quoted_printable_decode($value);
+ } else {
+ throw new Exception();
+ }
+ }
+
+ $param = $property['charset'];
+ if ($param !== null) {
+ $charset = strtoupper((string)$param);
+ if ($charset !== 'UTF-8') {
+ $value = mb_convert_encoding($value, 'UTF-8', $charset);
+ }
+ } else {
+ $value = StringUtil::convertToUTF8($value);
+ }
+
+ return $value;
+ */
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php b/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php
@@ -0,0 +1,19 @@
+add('PHOTO', array('a','b'));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php b/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php
@@ -0,0 +1,22 @@
+assertTrue($vcard->{'X-AWESOME'}->getValue());
+ $this->assertFalse($vcard->{'X-SUCKS'}->getValue());
+
+ $this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType());
+ $this->assertEquals($input, $vcard->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php b/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php
@@ -0,0 +1,50 @@
+createProperty('ORG');
+ $elem->setParts($arr);
+
+ $this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $elem->getValue());
+ $this->assertEquals(3, count($elem->getParts()));
+ $parts = $elem->getParts();
+ $this->assertEquals('Marketing;Sales', $parts[2]);
+
+ }
+
+ function testGetParts() {
+
+ $str = 'ABC\, Inc.;North American Division;Marketing\;Sales';
+
+ $vcard = new VCard();
+ $elem = $vcard->createProperty('ORG');
+ $elem->setRawMimeDirValue($str);
+
+ $this->assertEquals(3, count($elem->getParts()));
+ $parts = $elem->getParts();
+ $this->assertEquals('Marketing;Sales', $parts[2]);
+ }
+
+ function testGetPartsNull() {
+
+ $vcard = new VCard();
+ $elem = $vcard->createProperty('ORG', null);
+
+ $this->assertEquals(0, count($elem->getParts()));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php b/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php
@@ -0,0 +1,30 @@
+parse($input);
+
+ $this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'});
+
+ $this->assertEquals(array(
+ 0.234,
+ 1.245,
+ ), $result->{'X-FLOAT'}->getParts());
+
+ $this->assertEquals(
+ $input,
+ $result->serialize()
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php
@@ -0,0 +1,32 @@
+add('ATTENDEE', $input);
+
+ $this->assertEquals(
+ $expected,
+ $property->getNormalizedValue()
+ );
+
+ }
+
+ function values() {
+
+ return array(
+ array('mailto:a@b.com', 'mailto:a@b.com'),
+ array('mailto:a@b.com', 'MAILTO:a@b.com'),
+ array('/foo/bar', '/foo/bar'),
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php
@@ -0,0 +1,359 @@
+vcal = new VCalendar();
+
+ }
+
+ function testSetDateTime() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setDateTime($dt);
+
+ $this->assertEquals('19850704T013000', (string)$elem);
+ $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+ $this->assertNull($elem['VALUE']);
+
+ $this->assertTrue($elem->hasTime());
+
+ }
+
+ function testSetDateTimeLOCAL() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setDateTime($dt, $isFloating = true);
+
+ $this->assertEquals('19850704T013000', (string)$elem);
+ $this->assertNull($elem['TZID']);
+
+ $this->assertTrue($elem->hasTime());
+ }
+
+ function testSetDateTimeUTC() {
+
+ $tz = new \DateTimeZone('GMT');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setDateTime($dt);
+
+ $this->assertEquals('19850704T013000Z', (string)$elem);
+ $this->assertNull($elem['TZID']);
+
+ $this->assertTrue($elem->hasTime());
+ }
+
+ function testSetDateTimeLOCALTZ() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setDateTime($dt);
+
+ $this->assertEquals('19850704T013000', (string)$elem);
+ $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+
+ $this->assertTrue($elem->hasTime());
+ }
+
+ function testSetDateTimeDATE() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem['VALUE'] = 'DATE';
+ $elem->setDateTime($dt);
+
+ $this->assertEquals('19850704', (string)$elem);
+ $this->assertNull($elem['TZID']);
+ $this->assertEquals('DATE', (string)$elem['VALUE']);
+
+ $this->assertFalse($elem->hasTime());
+ }
+
+ function testSetValue() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setValue($dt);
+
+ $this->assertEquals('19850704T013000', (string)$elem);
+ $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+ $this->assertNull($elem['VALUE']);
+
+ $this->assertTrue($elem->hasTime());
+
+ }
+
+ function testSetValueArray() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt2 = new \DateTime('1985-07-04 02:30:00', $tz);
+ $dt1->setTimeZone($tz);
+ $dt2->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setValue(array($dt1, $dt2));
+
+ $this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
+ $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+ $this->assertNull($elem['VALUE']);
+
+ $this->assertTrue($elem->hasTime());
+
+ }
+
+ function testSetParts() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt1 = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt2 = new \DateTime('1985-07-04 02:30:00', $tz);
+ $dt1->setTimeZone($tz);
+ $dt2->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setParts(array($dt1, $dt2));
+
+ $this->assertEquals('19850704T013000,19850704T023000', (string)$elem);
+ $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']);
+ $this->assertNull($elem['VALUE']);
+
+ $this->assertTrue($elem->hasTime());
+
+ }
+ function testSetPartsStrings() {
+
+ $dt1 = '19850704T013000Z';
+ $dt2 = '19850704T023000Z';
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setParts(array($dt1, $dt2));
+
+ $this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem);
+ $this->assertNull($elem['VALUE']);
+
+ $this->assertTrue($elem->hasTime());
+
+ }
+
+
+ function testGetDateTimeCached() {
+
+ $tz = new \DateTimeZone('Europe/Amsterdam');
+ $dt = new \DateTime('1985-07-04 01:30:00', $tz);
+ $dt->setTimeZone($tz);
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $elem->setDateTime($dt);
+
+ $this->assertEquals($elem->getDateTime(), $dt);
+
+ }
+
+ function testGetDateTimeDateNULL() {
+
+ $elem = $this->vcal->createProperty('DTSTART');
+ $dt = $elem->getDateTime();
+
+ $this->assertNull($dt);
+
+ }
+
+ function testGetDateTimeDateDATE() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704');
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s'));
+
+ }
+
+ function testGetDateTimeDateDATEReferenceTimeZone() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704');
+
+ $tz = new \DateTimeZone('America/Toronto');
+ $dt = $elem->getDateTime($tz);
+ $dt->setTimeZone(new \DateTimeZone('UTC'));
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s'));
+
+ }
+
+ function testGetDateTimeDateFloating() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000');
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+
+ }
+
+ function testGetDateTimeDateFloatingReferenceTimeZone() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000');
+
+ $tz = new \DateTimeZone('America/Toronto');
+ $dt = $elem->getDateTime($tz);
+ $dt->setTimeZone(new \DateTimeZone('UTC'));
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s'));
+
+ }
+
+ function testGetDateTimeDateUTC() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000Z');
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+ $this->assertEquals('UTC', $dt->getTimeZone()->getName());
+
+ }
+
+ function testGetDateTimeDateLOCALTZ() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000');
+ $elem['TZID'] = 'Europe/Amsterdam';
+
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+ $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
+
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ function testGetDateTimeDateInvalid() {
+
+ $elem = $this->vcal->createProperty('DTSTART','bla');
+ $dt = $elem->getDateTime();
+
+ }
+
+ function testGetDateTimeWeirdTZ() {
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000');
+ $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
+
+
+ $event = $this->vcal->createComponent('VEVENT');
+ $event->add($elem);
+
+ $timezone = $this->vcal->createComponent('VTIMEZONE');
+ $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam';
+ $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam';
+
+ $this->vcal->add($event);
+ $this->vcal->add($timezone);
+
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+ $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName());
+
+ }
+
+ function testGetDateTimeBadTimeZone() {
+
+ $default = date_default_timezone_get();
+ date_default_timezone_set('Canada/Eastern');
+
+ $elem = $this->vcal->createProperty('DTSTART','19850704T013000');
+ $elem['TZID'] = 'Moon';
+
+
+ $event = $this->vcal->createComponent('VEVENT');
+ $event->add($elem);
+
+ $timezone = $this->vcal->createComponent('VTIMEZONE');
+ $timezone->TZID = 'Moon';
+ $timezone->{'X-LIC-LOCATION'} = 'Moon';
+
+
+ $this->vcal->add($event);
+ $this->vcal->add($timezone);
+
+ $dt = $elem->getDateTime();
+
+ $this->assertInstanceOf('DateTime', $dt);
+ $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s'));
+ $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName());
+ date_default_timezone_set($default);
+
+ }
+
+ function testUpdateValueParameter() {
+
+ $dtStart = $this->vcal->createProperty('DTSTART', new \DateTime('2013-06-07 15:05:00'));
+ $dtStart['VALUE'] = 'DATE';
+
+ $this->assertEquals("DTSTART;VALUE=DATE:20130607\r\n", $dtStart->serialize());
+
+ }
+
+ function testValidate() {
+
+ $exDate = $this->vcal->createProperty('EXDATE', '-00011130T143000Z');
+ $messages = $exDate->validate();
+ $this->assertEquals(1, count($messages));
+ $this->assertEquals(3, $messages[0]['level']);
+
+ }
+
+ /**
+ * This issue was discovered on the sabredav mailing list.
+ */
+ function testCreateDatePropertyThroughAdd() {
+
+ $vcal = new VCalendar();
+ $vevent = $vcal->add('VEVENT');
+
+ $dtstart = $vevent->add(
+ 'DTSTART',
+ new \DateTime('2014-03-07'),
+ array('VALUE' => 'DATE')
+ );
+
+ $this->assertEquals("DTSTART;VALUE=DATE:20140307\r\n", $dtstart->serialize());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php
@@ -0,0 +1,20 @@
+add('VEVENT', array('DURATION' => array('PT1H')));
+
+ $this->assertEquals(
+ new \DateInterval('PT1H'),
+ $event->{'DURATION'}->getDateInterval()
+ );
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php
@@ -0,0 +1,46 @@
+add('RRULE', 'FREQ=Daily');
+
+ $this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur);
+
+ $this->assertEquals(array('FREQ'=>'DAILY'), $recur->getParts());
+ $recur->setParts(array('freq'=>'MONTHLY'));
+
+ $this->assertEquals(array('FREQ'=>'MONTHLY'), $recur->getParts());
+
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ function testSetValueBadVal() {
+
+ $vcal = new VCalendar();
+ $recur = $vcal->add('RRULE', 'FREQ=Daily');
+ $recur->setValue(new \Exception());
+
+ }
+
+ function testSetSubParts() {
+
+ $vcal = new VCalendar();
+ $recur = $vcal->add('RRULE', array('FREQ'=>'DAILY', 'BYDAY'=>'mo,tu', 'BYMONTH' => array(0,1)));
+
+ $this->assertEquals(array(
+ 'FREQ'=>'DAILY',
+ 'BYDAY' => array('MO','TU'),
+ 'BYMONTH' => array(0,1),
+ ), $recur->getParts());
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/TextTest.php b/vendor/sabre/vobject/tests/VObject/Property/TextTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/TextTest.php
@@ -0,0 +1,96 @@
+'2.1',
+ 'PROP' => $propValue
+ ), false);
+
+ // Adding quoted-printable, because we're testing if it gets removed
+ // automatically.
+ $doc->PROP['ENCODING'] = 'QUOTED-PRINTABLE';
+ $doc->PROP['P1'] = 'V1';
+
+
+ $output = $doc->serialize();
+
+
+ $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\n$expected\r\nEND:VCARD\r\n", $output);
+
+ }
+
+ function testSerializeVCard21() {
+
+ $this->assertVCard21Serialization(
+ 'f;oo',
+ 'PROP;P1=V1:f;oo'
+ );
+
+ }
+
+ function testSerializeVCard21Array() {
+
+ $this->assertVCard21Serialization(
+ array('f;oo','bar'),
+ 'PROP;P1=V1:f\;oo;bar'
+ );
+
+ }
+ function testSerializeVCard21Fold() {
+
+ $this->assertVCard21Serialization(
+ str_repeat('x',80),
+ 'PROP;P1=V1:' . str_repeat('x',64) . "\r\n " . str_repeat('x',16)
+ );
+
+ }
+
+
+
+ function testSerializeQuotedPrintable() {
+
+ $this->assertVCard21Serialization(
+ "foo\r\nbar",
+ 'PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abar'
+ );
+ }
+
+ function testSerializeQuotedPrintableFold() {
+
+ $this->assertVCard21Serialization(
+ "foo\r\nbarxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abarxxxxxxxxxxxxxxxxxxxxxxxxxx=\r\n xxx"
+ );
+
+ }
+
+ function testValidateMinimumPropValue() {
+
+ $vcard = <<assertEquals(1, count($vcard->validate()));
+
+ $this->assertEquals(1, count($vcard->N->getParts()));
+
+ $vcard->validate(\Sabre\VObject\Node::REPAIR);
+
+ $this->assertEquals(5, count($vcard->N->getParts()));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php b/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php
@@ -0,0 +1,245 @@
+createProperty('BDAY', $input);
+
+ $this->assertEquals(array($output), $prop->getJsonValue());
+
+ }
+
+ function dates() {
+
+ return array(
+ array(
+ "19961022T140000",
+ "1996-10-22T14:00:00",
+ ),
+ array(
+ "--1022T1400",
+ "--10-22T14:00",
+ ),
+ array(
+ "---22T14",
+ "---22T14",
+ ),
+ array(
+ "19850412",
+ "1985-04-12",
+ ),
+ array(
+ "1985-04",
+ "1985-04",
+ ),
+ array(
+ "1985",
+ "1985",
+ ),
+ array(
+ "--0412",
+ "--04-12",
+ ),
+ array(
+ "T102200",
+ "T10:22:00",
+ ),
+ array(
+ "T1022",
+ "T10:22",
+ ),
+ array(
+ "T10",
+ "T10",
+ ),
+ array(
+ "T-2200",
+ "T-22:00",
+ ),
+ array(
+ "T102200Z",
+ "T10:22:00Z",
+ ),
+ array(
+ "T102200-0800",
+ "T10:22:00-0800",
+ ),
+ array(
+ "T--00",
+ "T--00",
+ ),
+ );
+
+ }
+
+ public function testSetParts() {
+
+ $vcard = new VObject\Component\VCard();
+
+ $prop = $vcard->createProperty('BDAY');
+ $prop->setParts(array(
+ new \DateTime('2014-04-02 18:37:00')
+ ));
+
+ $this->assertEquals('20140402T183700Z', $prop->getValue());
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetPartsTooMany() {
+
+ $vcard = new VObject\Component\VCard();
+
+ $prop = $vcard->createProperty('BDAY');
+ $prop->setParts(array(
+ 1,
+ 2
+ ));
+
+ }
+
+ public function testSetPartsString() {
+
+ $vcard = new VObject\Component\VCard();
+
+ $prop = $vcard->createProperty('BDAY');
+ $prop->setParts(array(
+ "20140402T183700Z"
+ ));
+
+ $this->assertEquals('20140402T183700Z', $prop->getValue());
+
+ }
+
+ public function testSetValueDateTime() {
+
+ $vcard = new VObject\Component\VCard();
+
+ $prop = $vcard->createProperty('BDAY');
+ $prop->setValue(
+ new \DateTime('2014-04-02 18:37:00')
+ );
+
+ $this->assertEquals('20140402T183700Z', $prop->getValue());
+
+ }
+
+ public function testSetDateTimeOffset() {
+
+ $vcard = new VObject\Component\VCard();
+
+ $prop = $vcard->createProperty('BDAY');
+ $prop->setValue(
+ new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto'))
+ );
+
+ $this->assertEquals('20140402T183700-0400', $prop->getValue());
+
+ }
+
+ public function testGetDateTime() {
+
+ $datetime = new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto'));
+
+ $vcard = new VObject\Component\VCard();
+ $prop = $vcard->createProperty('BDAY', $datetime);
+
+ $dt = $prop->getDateTime();
+ $this->assertEquals('2014-04-02T18:37:00-04:00', $dt->format('c'), "For some reason this one failed. Current default timezone is: " . date_default_timezone_get());
+
+ }
+
+ public function testGetDate() {
+
+ $datetime = new \DateTime('2014-04-02');
+
+ $vcard = new VObject\Component\VCard();
+ $prop = $vcard->createProperty('BDAY', $datetime, null, 'DATE');
+
+ $this->assertEquals('DATE', $prop->getValueType());
+ $this->assertEquals('BDAY:20140402', rtrim($prop->serialize()));
+
+ }
+
+ public function testGetDateIncomplete() {
+
+ $datetime = '--0407';
+
+ $vcard = new VObject\Component\VCard();
+ $prop = $vcard->add('BDAY', $datetime);
+
+ $dt = $prop->getDateTime();
+ // Note: if the year changes between the last line and the next line of
+ // code, this test may fail.
+ //
+ // If that happens, head outside and have a drink.
+ $current = new \DateTime('now');
+ $year = $current->format('Y');
+
+ $this->assertEquals($year . '0407', $dt->format('Ymd'));
+
+ }
+
+ public function testGetDateIncompleteFromVCard() {
+
+ $vcard = <<BDAY;
+
+ $dt = $prop->getDateTime();
+ // Note: if the year changes between the last line and the next line of
+ // code, this test may fail.
+ //
+ // If that happens, head outside and have a drink.
+ $current = new \DateTime('now');
+ $year = $current->format('Y');
+
+ $this->assertEquals($year . '0407', $dt->format('Ymd'));
+
+ }
+
+ public function testValidate() {
+
+ $datetime = '--0407';
+
+ $vcard = new VObject\Component\VCard();
+ $prop = $vcard->add('BDAY', $datetime);
+
+ $this->assertEquals(array(), $prop->validate());
+
+ }
+
+ public function testValidateBroken() {
+
+ $datetime = '123';
+
+ $vcard = new VObject\Component\VCard();
+ $prop = $vcard->add('BDAY', $datetime);
+
+ $this->assertEquals(array(array(
+ 'level' => 3,
+ 'message' => 'The supplied value (123) is not a correct DATE-AND-OR-TIME property',
+ 'node' => $prop,
+ )), $prop->validate());
+
+ }
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php b/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php
@@ -0,0 +1,48 @@
+parse($input);
+
+ $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG);
+
+ $this->assertEquals('nl', $result->LANG->getValue());
+
+ $this->assertEquals(
+ $input,
+ $result->serialize()
+ );
+
+ }
+
+ function testChangeAndSerialize() {
+
+ $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n";
+ $mimeDir = new VObject\Parser\MimeDir($input);
+
+ $result = $mimeDir->parse($input);
+
+ $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG);
+ // This replicates what the vcard converter does and triggered a bug in
+ // the past.
+ $result->LANG->setValue(array('de'));
+
+ $this->assertEquals('de', $result->LANG->getValue());
+
+ $expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:de\r\nEND:VCARD\r\n";
+ $this->assertEquals(
+ $expected,
+ $result->serialize()
+ );
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/PropertyTest.php b/vendor/sabre/vobject/tests/VObject/PropertyTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/PropertyTest.php
@@ -0,0 +1,411 @@
+createProperty('propname','propvalue');
+ $this->assertEquals('PROPNAME', $property->name);
+ $this->assertEquals('propvalue', $property->__toString());
+ $this->assertEquals('propvalue', (string)$property);
+ $this->assertEquals('propvalue', $property->getValue());
+
+ }
+
+ function testCreate() {
+
+ $cal = new VCalendar();
+
+ $params = array(
+ 'param1' => 'value1',
+ 'param2' => 'value2',
+ );
+
+ $property = $cal->createProperty('propname','propvalue', $params);
+
+ $this->assertEquals('value1', $property['param1']->getValue());
+ $this->assertEquals('value2', $property['param2']->getValue());
+
+ }
+
+ function testSetValue() {
+
+ $cal = new VCalendar();
+
+ $property = $cal->createProperty('propname','propvalue');
+ $property->setValue('value2');
+
+ $this->assertEquals('PROPNAME', $property->name);
+ $this->assertEquals('value2', $property->__toString());
+
+ }
+
+ function testParameterExists() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+
+ $this->assertTrue(isset($property['PARAMNAME']));
+ $this->assertTrue(isset($property['paramname']));
+ $this->assertFalse(isset($property['foo']));
+
+ }
+
+ function testParameterGet() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+
+ $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
+
+ }
+
+ function testParameterNotExists() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+
+ $this->assertInternalType('null',$property['foo']);
+
+ }
+
+ function testParameterMultiple() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+ $property->add('paramname', 'paramvalue');
+
+ $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']);
+ $this->assertEquals(2,count($property['paramname']->getParts()));
+
+ }
+
+ function testSetParameterAsString() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+
+ $this->assertEquals(1,count($property->parameters()));
+ $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']);
+ $this->assertEquals('PARAMNAME',$property->parameters['PARAMNAME']->name);
+ $this->assertEquals('paramvalue',$property->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testUnsetParameter() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $property['paramname'] = 'paramvalue';
+
+ unset($property['PARAMNAME']);
+ $this->assertEquals(0,count($property->parameters()));
+
+ }
+
+ function testSerialize() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+
+ $this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize());
+
+ }
+
+ function testSerializeParam() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue', array(
+ 'paramname' => 'paramvalue',
+ 'paramname2' => 'paramvalue2',
+ ));
+
+ $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize());
+
+ }
+
+ function testSerializeNewLine() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('SUMMARY',"line1\nline2");
+
+ $this->assertEquals("SUMMARY:line1\\nline2\r\n",$property->serialize());
+
+ }
+
+ function testSerializeLongLine() {
+
+ $cal = new VCalendar();
+ $value = str_repeat('!',200);
+ $property = $cal->createProperty('propname',$value);
+
+ $expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n";
+
+ $this->assertEquals($expected,$property->serialize());
+
+ }
+
+ function testSerializeUTF8LineFold() {
+
+ $cal = new VCalendar();
+ $value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a
+ $property = $cal->createProperty('propname', $value);
+ $expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n";
+ $this->assertEquals($expected, $property->serialize());
+
+ }
+
+ function testGetIterator() {
+
+ $cal = new VCalendar();
+ $it = new ElementList(array());
+ $property = $cal->createProperty('propname','propvalue');
+ $property->setIterator($it);
+ $this->assertEquals($it,$property->getIterator());
+
+ }
+
+
+ function testGetIteratorDefault() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('propname','propvalue');
+ $it = $property->getIterator();
+ $this->assertTrue($it instanceof ElementList);
+ $this->assertEquals(1,count($it));
+
+ }
+
+ function testAddScalar() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('EMAIL');
+
+ $property->add('myparam','value');
+
+ $this->assertEquals(1, count($property->parameters()));
+
+ $this->assertTrue($property->parameters['MYPARAM'] instanceof Parameter);
+ $this->assertEquals('MYPARAM',$property->parameters['MYPARAM']->name);
+ $this->assertEquals('value',$property->parameters['MYPARAM']->getValue());
+
+ }
+
+ function testAddParameter() {
+
+ $cal = new VCalendar();
+ $prop = $cal->createProperty('EMAIL');
+
+ $prop->add('MYPARAM','value');
+
+ $this->assertEquals(1, count($prop->parameters()));
+ $this->assertEquals('MYPARAM',$prop['myparam']->name);
+
+ }
+
+ function testAddParameterTwice() {
+
+ $cal = new VCalendar();
+ $prop = $cal->createProperty('EMAIL');
+
+ $prop->add('MYPARAM', 'value1');
+ $prop->add('MYPARAM', 'value2');
+
+ $this->assertEquals(1, count($prop->parameters));
+ $this->assertEquals(2, count($prop->parameters['MYPARAM']->getParts()));
+
+ $this->assertEquals('MYPARAM',$prop['MYPARAM']->name);
+
+ }
+
+
+ function testClone() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('EMAIL','value');
+ $property['FOO'] = 'BAR';
+
+ $property2 = clone $property;
+
+ $property['FOO'] = 'BAZ';
+ $this->assertEquals('BAR', (string)$property2['FOO']);
+
+ }
+
+ function testCreateParams() {
+
+ $cal = new VCalendar();
+ $property = $cal->createProperty('X-PROP','value', array(
+ 'param1' => 'value1',
+ 'param2' => array('value2', 'value3')
+ ));
+
+ $this->assertEquals(1, count($property['PARAM1']->getParts()));
+ $this->assertEquals(2, count($property['PARAM2']->getParts()));
+
+ }
+
+ function testValidateNonUTF8() {
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty('X-PROP', "Bla\x00");
+ $result = $property->validate(Property::REPAIR);
+
+ $this->assertEquals('Property contained a control character (0x00)', $result[0]['message']);
+ $this->assertEquals('Bla', $property->getValue());
+
+ }
+
+ function testValidateControlChars() {
+
+ $s = "chars[";
+ foreach (array(
+ 0x7F, 0x5E, 0x5C, 0x3B, 0x3A, 0x2C, 0x22, 0x20,
+ 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18,
+ 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
+ 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
+ ) as $c) {
+ $s .= sprintf('%02X(%c)', $c, $c);
+ }
+ $s .= "]end";
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty('X-PROP', $s);
+ $result = $property->validate(Property::REPAIR);
+
+ $this->assertEquals('Property contained a control character (0x7f)', $result[0]['message']);
+ $this->assertEquals("chars[7F()5E(^)5C(\\\\)3B(\\;)3A(:)2C(\\,)22(\")20( )1F()1E()1D()1C()1B()1A()19()18()17()16()15()14()13()12()11()10()0F()0E()0D()0C()0B()0A(\\n)09( )08()07()06()05()04()03()02()01()00()]end", $property->getRawMimeDirValue());
+
+ }
+
+ function testValidateBadPropertyName() {
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty("X_*&PROP*", "Bla");
+ $result = $property->validate(Property::REPAIR);
+
+ $this->assertEquals($result[0]['message'], 'The propertyname: X_*&PROP* contains invalid characters. Only A-Z, 0-9 and - are allowed');
+ $this->assertEquals('X-PROP', $property->name);
+
+ }
+
+ function testGetValue() {
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty("SUMMARY", null);
+ $this->assertEquals(array(), $property->getParts());
+ $this->assertNull($property->getValue());
+
+ $property->setValue(array());
+ $this->assertEquals(array(), $property->getParts());
+ $this->assertNull($property->getValue());
+
+ $property->setValue(array(1));
+ $this->assertEquals(array(1), $property->getParts());
+ $this->assertEquals(1, $property->getValue());
+
+ $property->setValue(array(1,2));
+ $this->assertEquals(array(1,2), $property->getParts());
+ $this->assertEquals('1,2', $property->getValue());
+
+ $property->setValue('str');
+ $this->assertEquals(array('str'), $property->getParts());
+ $this->assertEquals('str', $property->getValue());
+ }
+
+ /**
+ * ElementList should reject this.
+ *
+ * @expectedException \LogicException
+ */
+ function testArrayAccessSetInt() {
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty("X-PROP", null);
+
+ $calendar->add($property);
+ $calendar->{'X-PROP'}[0] = 'Something!';
+
+ }
+
+ /**
+ * ElementList should reject this.
+ *
+ * @expectedException \LogicException
+ */
+ function testArrayAccessUnsetInt() {
+
+ $calendar = new VCalendar();
+ $property = $calendar->createProperty("X-PROP", null);
+
+ $calendar->add($property);
+ unset($calendar->{'X-PROP'}[0]);
+
+ }
+
+ function testValidateBadEncoding() {
+
+ $document = new VCalendar();
+ $property = $document->add('X-FOO','value');
+ $property['ENCODING'] = 'invalid';
+
+ $result = $property->validate();
+
+ $this->assertEquals('ENCODING=INVALID is not valid for this document type.', $result[0]['message']);
+ $this->assertEquals(1, $result[0]['level']);
+
+ }
+
+ function testValidateBadEncodingVCard4() {
+
+ $document = new VCard(array('VERSION' => '4.0'));
+ $property = $document->add('X-FOO','value');
+ $property['ENCODING'] = 'BASE64';
+
+ $result = $property->validate();
+
+ $this->assertEquals('ENCODING parameter is not valid in vCard 4.', $result[0]['message']);
+ $this->assertEquals(1, $result[0]['level']);
+
+ }
+
+ function testValidateBadEncodingVCard3() {
+
+ $document = new VCard(array('VERSION' => '3.0'));
+ $property = $document->add('X-FOO','value');
+ $property['ENCODING'] = 'BASE64';
+
+ $result = $property->validate();
+
+ $this->assertEquals('ENCODING=BASE64 is not valid for this document type.', $result[0]['message']);
+ $this->assertEquals(1, $result[0]['level']);
+
+ }
+
+ function testValidateBadEncodingVCard21() {
+
+ $document = new VCard(array('VERSION' => '2.1'));
+ $property = $document->add('X-FOO','value');
+ $property['ENCODING'] = 'B';
+
+ $result = $property->validate();
+
+ $this->assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']);
+ $this->assertEquals(1, $result[0]['level']);
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/ReaderTest.php b/vendor/sabre/vobject/tests/VObject/ReaderTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/ReaderTest.php
@@ -0,0 +1,449 @@
+assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(0, count($result->children));
+
+ }
+ function testReadStream() {
+
+ $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR";
+
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, $data);
+ rewind($stream);
+
+ $result = Reader::read($stream);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(0, count($result->children));
+
+ }
+
+ function testReadComponentUnixNewLine() {
+
+ $data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
+
+ $result = Reader::read($data);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(0, count($result->children));
+
+ }
+
+ function testReadComponentLineFold() {
+
+ $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR";
+
+ $result = Reader::read($data);
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(0, count($result->children));
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testReadCorruptComponent() {
+
+ $data = "BEGIN:VCALENDAR\r\nEND:FOO";
+
+ $result = Reader::read($data);
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testReadCorruptSubComponent() {
+
+ $data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR";
+
+ $result = Reader::read($data);
+
+ }
+
+ function testReadProperty() {
+
+ $data = "BEGIN:VCALENDAR\r\nSUMMARY:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->SUMMARY;
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('SUMMARY', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+
+ }
+
+ function testReadPropertyWithNewLine() {
+
+ $data = "BEGIN:VCALENDAR\r\nSUMMARY:Line1\\nLine2\\NLine3\\\\Not the 4th line!\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->SUMMARY;
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('SUMMARY', $result->name);
+ $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue());
+
+ }
+
+ function testReadMappedProperty() {
+
+ $data = "BEGIN:VCALENDAR\r\nDTSTART:20110529\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->DTSTART;
+ $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result);
+ $this->assertEquals('DTSTART', $result->name);
+ $this->assertEquals('20110529', $result->getValue());
+
+ }
+
+ function testReadMappedPropertyGrouped() {
+
+ $data = "BEGIN:VCALENDAR\r\nfoo.DTSTART:20110529\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->DTSTART;
+ $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result);
+ $this->assertEquals('DTSTART', $result->name);
+ $this->assertEquals('20110529', $result->getValue());
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testReadBrokenLine() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue";
+ $result = Reader::read($data);
+
+ }
+
+ function testReadPropertyInComponent() {
+
+ $data = array(
+ "BEGIN:VCALENDAR",
+ "PROPNAME:propValue",
+ "END:VCALENDAR"
+ );
+
+ $result = Reader::read(implode("\r\n",$data));
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]);
+ $this->assertEquals('PROPNAME', $result->children[0]->name);
+ $this->assertEquals('propValue', $result->children[0]->getValue());
+
+ }
+
+ function testReadNestedComponent() {
+
+ $data = array(
+ "BEGIN:VCALENDAR",
+ "BEGIN:VTIMEZONE",
+ "BEGIN:DAYLIGHT",
+ "END:DAYLIGHT",
+ "END:VTIMEZONE",
+ "END:VCALENDAR"
+ );
+
+ $result = Reader::read(implode("\r\n",$data));
+
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(1, count($result->children()));
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]);
+ $this->assertEquals('VTIMEZONE', $result->children[0]->name);
+ $this->assertEquals(1, count($result->children[0]->children()));
+ $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]);
+ $this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name);
+
+
+ }
+
+ function testReadPropertyParameter() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testReadPropertyRepeatingParameter() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;N=1;N=2;N=3,4;N=\"5\",6;N=\"7,8\";N=9,10;N=^'11^':propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('N', $result->parameters['N']->name);
+ $this->assertEquals('1,2,3,4,5,6,7,8,9,10,"11"', $result->parameters['N']->getValue());
+ $this->assertEquals(array(1,2,3,4,5,6,"7,8",9,10,'"11"'), $result->parameters['N']->getParts());
+
+ }
+
+ function testReadPropertyRepeatingNamelessGuessedParameter() {
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;WORK;VOICE;PREF:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('TYPE', $result->parameters['TYPE']->name);
+ $this->assertEquals('WORK,VOICE,PREF', $result->parameters['TYPE']->getValue());
+ $this->assertEquals(array('WORK', 'VOICE', 'PREF'), $result->parameters['TYPE']->getParts());
+
+ }
+
+ function testReadPropertyNoName() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PRODIGY:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('TYPE', $result->parameters['TYPE']->name);
+ $this->assertTrue($result->parameters['TYPE']->noName);
+ $this->assertEquals('PRODIGY', $result->parameters['TYPE']);
+
+ }
+
+ function testReadPropertyParameterExtraColon() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue:anotherrandomstring', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testReadProperty2Parameters() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(2, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
+ $this->assertEquals('PARAMNAME2', $result->parameters['PARAMNAME2']->name);
+ $this->assertEquals('paramvalue2', $result->parameters['PARAMNAME2']->getValue());
+
+ }
+
+ function testReadPropertyParameterQuoted() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"paramvalue\":propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->PROPNAME;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testReadPropertyParameterNewLines() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue1^nvalue2^^nvalue3:propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+
+ $result = $result->propname;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals("paramvalue1\nvalue2^nvalue3", $result->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testReadPropertyParameterQuotedColon() {
+
+ $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"param:value\":propValue\r\nEND:VCALENDAR";
+ $result = Reader::read($data);
+ $result = $result->propname;
+
+ $this->assertInstanceOf('Sabre\\VObject\\Property', $result);
+ $this->assertEquals('PROPNAME', $result->name);
+ $this->assertEquals('propValue', $result->getValue());
+ $this->assertEquals(1, count($result->parameters()));
+ $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name);
+ $this->assertEquals('param:value', $result->parameters['PARAMNAME']->getValue());
+
+ }
+
+ function testReadForgiving() {
+
+ $data = array(
+ "BEGIN:VCALENDAR",
+ "X_PROP:propValue",
+ "END:VCALENDAR"
+ );
+
+ $caught = false;
+ try {
+ $result = Reader::read(implode("\r\n",$data));
+ } catch (ParseException $e) {
+ $caught = true;
+ }
+
+ $this->assertEquals(true, $caught);
+
+ $result = Reader::read(implode("\r\n",$data), Reader::OPTION_FORGIVING);
+
+ $expected = implode("\r\n", array(
+ "BEGIN:VCALENDAR",
+ "X_PROP:propValue",
+ "END:VCALENDAR",
+ ""
+ ));
+
+ $this->assertEquals($expected, $result->serialize());
+
+ }
+
+ function testReadWithInvalidLine() {
+
+ $data = array(
+ "BEGIN:VCALENDAR",
+ "DESCRIPTION:propValue",
+ "Yes, we've actually seen a file with non-idented property values on multiple lines",
+ "END:VCALENDAR"
+ );
+
+ $caught = false;
+ try {
+ $result = Reader::read(implode("\r\n",$data));
+ } catch (ParseException $e) {
+ $caught = true;
+ }
+
+ $this->assertEquals(true, $caught);
+
+ $result = Reader::read(implode("\r\n",$data), Reader::OPTION_IGNORE_INVALID_LINES);
+
+ $expected = implode("\r\n", array(
+ "BEGIN:VCALENDAR",
+ "DESCRIPTION:propValue",
+ "END:VCALENDAR",
+ ""
+ ));
+
+ $this->assertEquals($expected, $result->serialize());
+
+ }
+
+ /**
+ * Reported as Issue 32.
+ *
+ * @expectedException \Sabre\VObject\ParseException
+ */
+ public function testReadIncompleteFile() {
+
+ $input = <<assertInstanceOf('Sabre\\VObject\\Component', $result);
+ $this->assertEquals('VCALENDAR', $result->name);
+ $this->assertEquals(0, count($result->children));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php
@@ -0,0 +1,59 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11'));
+
+ foreach ($vcal->VEVENT as $event) {
+ $dates[] = $event->DTSTART->getValue();
+ }
+
+ $expectedDates = array(
+ "20130929T160000Z",
+ "20131006T160000Z",
+ "20131013T160000Z",
+ "20131020T160000Z",
+ "20131027T160000Z",
+ "20140907T160000Z"
+ );
+
+ $this->assertEquals($expectedDates, $dates, 'Recursed dates are restricted by month');
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php
@@ -0,0 +1,61 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01'));
+
+ foreach ($vcal->VEVENT as $event) {
+ $dates[] = $event->DTSTART->getValue();
+ }
+
+ $expectedDates = array(
+ "20150101T160000Z",
+ "20150122T160000Z",
+ "20150219T160000Z",
+ "20150319T160000Z",
+ "20150423T150000Z",
+ "20150521T150000Z",
+ "20150618T150000Z",
+ "20150723T150000Z",
+ "20150820T150000Z",
+ "20150917T150000Z",
+ "20151022T150000Z",
+ "20151119T160000Z",
+ "20151224T160000Z",
+ );
+
+ $this->assertEquals($expectedDates, $dates);
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php
@@ -0,0 +1,122 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'));
+
+ $result = $vcal->serialize();
+
+ $output = <<assertEquals($output, str_replace("\r", "", $result));
+
+ }
+
+ function testExpandWithReferenceTimezone() {
+
+ $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'), new \DateTimeZone('Europe/Berlin'));
+
+ $result = $vcal->serialize();
+
+ $output = <<assertEquals($output, str_replace("\r", "", $result));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php
@@ -0,0 +1,54 @@
+VEVENT->UID);
+
+ while($it->valid()) {
+ $it->next();
+ }
+
+ // If we got here, it means we were successful. The bug that was in the
+ // system before would fail on the 5th tuesday of the month, if the 5th
+ // tuesday did not exist.
+ $this->assertTrue(true);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php
@@ -0,0 +1,64 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01'));
+
+ $result = $vcal->serialize();
+
+ $output = <<assertEquals($output, str_replace("\r", "", $result));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php
@@ -0,0 +1,99 @@
+vcal = new VCalendar();
+
+ }
+
+ /**
+ * This bug came from a Fruux customer. This would result in a never-ending
+ * request.
+ */
+ function testFastForwardTooFar() {
+
+ $ev = $this->vcal->createComponent('VEVENT');
+ $ev->UID = 'foobar';
+ $ev->DTSTART = '20090420T180000Z';
+ $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1';
+
+ $this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00')));
+
+ }
+
+ /**
+ * Different bug, also likely an infinite loop.
+ */
+ function testYearlyByMonthLoop() {
+
+ $ev = $this->vcal->createComponent('VEVENT');
+ $ev->UID = 'uuid';
+ $ev->DTSTART = '20120101T154500';
+ $ev->DTSTART['TZID'] = 'Europe/Berlin';
+ $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
+ $ev->DTEND = '20120101T164500';
+ $ev->DTEND['TZID'] = 'Europe/Berlin';
+
+ // This recurrence rule by itself is a yearly rule that should happen
+ // every february.
+ //
+ // The BYDAY part expands this to every day of the month, but the
+ // BYSETPOS limits this to only the 1st day of the month. Very crazy
+ // way to specify this, and could have certainly been a lot easier.
+ $this->vcal->add($ev);
+
+ $it = new Recur\EventIterator($this->vcal,'uuid');
+ $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
+
+ $collect = array();
+
+ while($it->valid()) {
+ $collect[] = $it->getDTSTART();
+ if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
+ break;
+ }
+ $it->next();
+
+ }
+
+ $this->assertEquals(
+ array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))),
+ $collect
+ );
+
+ }
+
+ /**
+ * Something, somewhere produced an ics with an interval set to 0. Because
+ * this means we increase the current day (or week, month) by 0, this also
+ * results in an infinite loop.
+ *
+ * @expectedException InvalidArgumentException
+ * @return void
+ */
+ function testZeroInterval() {
+
+ $ev = $this->vcal->createComponent('VEVENT');
+ $ev->UID = 'uuid';
+ $ev->DTSTART = '20120824T145700Z';
+ $ev->RRULE = 'FREQ=YEARLY;INTERVAL=0';
+ $this->vcal->add($ev);
+
+ $it = new Recur\EventIterator($this->vcal,'uuid');
+ $it->fastForward(new DateTime('2013-01-01 23:00:00', new DateTimeZone('UTC')));
+
+ // if we got this far.. it means we are no longer infinitely looping
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php
@@ -0,0 +1,49 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $it = new Recur\EventIterator($vcal, 'foo');
+
+ $result = iterator_to_array($it);
+
+ $tz = new DateTimeZone('Europe/Moscow');
+
+ $expected = array(
+ new DateTime('2013-07-10 11:00:00', $tz),
+ new DateTime('2013-07-12 11:00:00', $tz),
+ new DateTime('2013-07-13 11:00:00', $tz),
+ );
+
+ $this->assertEquals($expected, $result);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php
@@ -0,0 +1,128 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724');
+
+ $result = array();
+ foreach($it as $instance) {
+
+ $result[] = $instance;
+
+ }
+
+ $tz = new DateTimeZone('Europe/Brussels');
+
+ $this->assertEquals(array(
+ new DateTime('2013-07-15 09:00:00', $tz),
+ new DateTime('2013-07-16 07:00:00', $tz),
+ new DateTime('2013-07-17 07:00:00', $tz),
+ new DateTime('2013-07-18 09:00:00', $tz),
+ new DateTime('2013-07-19 07:00:00', $tz),
+ ), $result);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php
@@ -0,0 +1,1427 @@
+createComponent('VEVENT');
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07'));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $this->assertTrue($it->isInfinite());
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @depends testValues
+ */
+ function testInvalidFreq() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+ $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z';
+ $ev->UID = 'foo';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testVCalendarNoUID() {
+
+ $vcal = new VCalendar();
+ $it = new EventIterator($vcal);
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testVCalendarInvalidUID() {
+
+ $vcal = new VCalendar();
+ $it = new EventIterator($vcal,'foo');
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testHourly() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07 12:00:00', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,$ev->uid);
+
+ // Max is to prevent overflow
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07 12:00:00', $tz),
+ new DateTime('2011-10-07 15:00:00', $tz),
+ new DateTime('2011-10-07 18:00:00', $tz),
+ new DateTime('2011-10-07 21:00:00', $tz),
+ new DateTime('2011-10-08 00:00:00', $tz),
+ new DateTime('2011-10-08 03:00:00', $tz),
+ new DateTime('2011-10-08 06:00:00', $tz),
+ new DateTime('2011-10-08 09:00:00', $tz),
+ new DateTime('2011-10-08 12:00:00', $tz),
+ new DateTime('2011-10-08 15:00:00', $tz),
+ new DateTime('2011-10-08 18:00:00', $tz),
+ new DateTime('2011-10-08 21:00:00', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testDaily() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,$ev->uid);
+
+ // Max is to prevent overflow
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2011-10-10', $tz),
+ new DateTime('2011-10-13', $tz),
+ new DateTime('2011-10-16', $tz),
+ new DateTime('2011-10-19', $tz),
+ new DateTime('2011-10-22', $tz),
+ new DateTime('2011-10-25', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testNoRRULE() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,$ev->uid);
+
+ // Max is to prevent overflow
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testDailyByDayByHour() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-08 06:00:00', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new datetime('2011-10-08 06:00:00', $tz),
+ new datetime('2011-10-08 07:00:00', $tz),
+ new datetime('2011-10-09 06:00:00', $tz),
+ new datetime('2011-10-09 07:00:00', $tz),
+ new datetime('2011-10-15 06:00:00', $tz),
+ new datetime('2011-10-15 07:00:00', $tz),
+ new datetime('2011-10-16 06:00:00', $tz),
+ new datetime('2011-10-16 07:00:00', $tz),
+ new datetime('2011-10-22 06:00:00', $tz),
+ new datetime('2011-10-22 07:00:00', $tz),
+ new datetime('2011-10-23 06:00:00', $tz),
+ new datetime('2011-10-23 07:00:00', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testDailyByHour() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2012-10-11 12:00:00', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new datetime('2012-10-11 12:00:00', $tz),
+ new datetime('2012-10-11 13:00:00', $tz),
+ new datetime('2012-10-11 14:00:00', $tz),
+ new datetime('2012-10-11 15:00:00', $tz),
+ new datetime('2012-10-13 10:00:00', $tz),
+ new datetime('2012-10-13 11:00:00', $tz),
+ new datetime('2012-10-13 12:00:00', $tz),
+ new datetime('2012-10-13 13:00:00', $tz),
+ new datetime('2012-10-13 14:00:00', $tz),
+ new datetime('2012-10-13 15:00:00', $tz),
+ new datetime('2012-10-15 10:00:00', $tz),
+ new datetime('2012-10-15 11:00:00', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testDailyByDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2011-10-11', $tz),
+ new DateTime('2011-10-19', $tz),
+ new DateTime('2011-10-21', $tz),
+ new DateTime('2011-10-25', $tz),
+ new DateTime('2011-11-02', $tz),
+ new DateTime('2011-11-04', $tz),
+ new DateTime('2011-11-08', $tz),
+ new DateTime('2011-11-16', $tz),
+ new DateTime('2011-11-18', $tz),
+ new DateTime('2011-11-22', $tz),
+ new DateTime('2011-11-30', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testWeekly() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Max is to prevent overflow
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2011-10-21', $tz),
+ new DateTime('2011-11-04', $tz),
+ new DateTime('2011-11-18', $tz),
+ new DateTime('2011-12-02', $tz),
+ new DateTime('2011-12-16', $tz),
+ new DateTime('2011-12-30', $tz),
+ new DateTime('2012-01-13', $tz),
+ new DateTime('2012-01-27', $tz),
+ new DateTime('2012-02-10', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testWeeklyByDayByHour() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07 08:00:00', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 15;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07 08:00:00', $tz),
+ new DateTime('2011-10-07 09:00:00', $tz),
+ new DateTime('2011-10-07 10:00:00', $tz),
+ new DateTime('2011-10-18 08:00:00', $tz),
+ new DateTime('2011-10-18 09:00:00', $tz),
+ new DateTime('2011-10-18 10:00:00', $tz),
+ new DateTime('2011-10-19 08:00:00', $tz),
+ new DateTime('2011-10-19 09:00:00', $tz),
+ new DateTime('2011-10-19 10:00:00', $tz),
+ new DateTime('2011-10-21 08:00:00', $tz),
+ new DateTime('2011-10-21 09:00:00', $tz),
+ new DateTime('2011-10-21 10:00:00', $tz),
+ new DateTime('2011-11-01 08:00:00', $tz),
+ new DateTime('2011-11-01 09:00:00', $tz),
+ new DateTime('2011-11-01 10:00:00', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testWeeklyByDaySpecificHour() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07 18:00:00', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07 18:00:00', $tz),
+ new DateTime('2011-10-18 18:00:00', $tz),
+ new DateTime('2011-10-19 18:00:00', $tz),
+ new DateTime('2011-10-21 18:00:00', $tz),
+ new DateTime('2011-11-01 18:00:00', $tz),
+ new DateTime('2011-11-02 18:00:00', $tz),
+ new DateTime('2011-11-04 18:00:00', $tz),
+ new DateTime('2011-11-15 18:00:00', $tz),
+ new DateTime('2011-11-16 18:00:00', $tz),
+ new DateTime('2011-11-18 18:00:00', $tz),
+ new DateTime('2011-11-29 18:00:00', $tz),
+ new DateTime('2011-11-30 18:00:00', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testWeeklyByDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // Grabbing the next 12 items
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2011-10-18', $tz),
+ new DateTime('2011-10-19', $tz),
+ new DateTime('2011-10-21', $tz),
+ new DateTime('2011-11-01', $tz),
+ new DateTime('2011-11-02', $tz),
+ new DateTime('2011-11-04', $tz),
+ new DateTime('2011-11-15', $tz),
+ new DateTime('2011-11-16', $tz),
+ new DateTime('2011-11-18', $tz),
+ new DateTime('2011-11-29', $tz),
+ new DateTime('2011-11-30', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testMonthly() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-12-05', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 14;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-12-05', $tz),
+ new DateTime('2012-03-05', $tz),
+ new DateTime('2012-06-05', $tz),
+ new DateTime('2012-09-05', $tz),
+ new DateTime('2012-12-05', $tz),
+ ),
+ $result
+ );
+
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testMonthlyEndOfMonth() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-12-31', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 14;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-12-31', $tz),
+ new DateTime('2012-08-31', $tz),
+ new DateTime('2012-10-31', $tz),
+ new DateTime('2012-12-31', $tz),
+ new DateTime('2013-08-31', $tz),
+ new DateTime('2013-10-31', $tz),
+ new DateTime('2013-12-31', $tz),
+ new DateTime('2014-08-31', $tz),
+ new DateTime('2014-10-31', $tz),
+ new DateTime('2014-12-31', $tz),
+ new DateTime('2015-08-31', $tz),
+ new DateTime('2015-10-31', $tz),
+ ),
+ $result
+ );
+
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testMonthlyByMonthDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 14;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-01-01', $tz),
+ new DateTime('2011-01-25', $tz),
+ new DateTime('2011-01-31', $tz),
+ new DateTime('2011-06-01', $tz),
+ new DateTime('2011-06-24', $tz),
+ new DateTime('2011-11-01', $tz),
+ new DateTime('2011-11-24', $tz),
+ new DateTime('2012-04-01', $tz),
+ new DateTime('2012-04-24', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * A pretty slow test. Had to be marked as 'medium' for phpunit to not die
+ * after 1 second. Would be good to optimize later.
+ *
+ * @depends testValues
+ * @medium
+ */
+ function testMonthlyByDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-01-03', $tz),
+ new DateTime('2011-01-05', $tz),
+ new DateTime('2011-01-10', $tz),
+ new DateTime('2011-01-17', $tz),
+ new DateTime('2011-01-18', $tz),
+ new DateTime('2011-01-20', $tz),
+ new DateTime('2011-01-24', $tz),
+ new DateTime('2011-01-31', $tz),
+ new DateTime('2011-03-02', $tz),
+ new DateTime('2011-03-07', $tz),
+ new DateTime('2011-03-14', $tz),
+ new DateTime('2011-03-17', $tz),
+ new DateTime('2011-03-21', $tz),
+ new DateTime('2011-03-22', $tz),
+ new DateTime('2011-03-28', $tz),
+ new DateTime('2011-05-02', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testMonthlyByDayByMonthDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-08-01', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-08-01', $tz),
+ new DateTime('2012-10-01', $tz),
+ new DateTime('2013-04-01', $tz),
+ new DateTime('2013-07-01', $tz),
+ new DateTime('2014-09-01', $tz),
+ new DateTime('2014-12-01', $tz),
+ new DateTime('2015-06-01', $tz),
+ new DateTime('2016-02-01', $tz),
+ new DateTime('2016-08-01', $tz),
+ new DateTime('2017-05-01', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testMonthlyByDayBySetPos() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-01-03', $tz),
+ new DateTime('2011-01-31', $tz),
+ new DateTime('2011-02-01', $tz),
+ new DateTime('2011-02-28', $tz),
+ new DateTime('2011-03-01', $tz),
+ new DateTime('2011-03-31', $tz),
+ new DateTime('2011-04-01', $tz),
+ new DateTime('2011-04-29', $tz),
+ new DateTime('2011-05-02', $tz),
+ new DateTime('2011-05-31', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testYearly() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-01-01', $tz),
+ new DateTime('2014-01-01', $tz),
+ new DateTime('2017-01-01', $tz),
+ new DateTime('2020-01-01', $tz),
+ new DateTime('2023-01-01', $tz),
+ new DateTime('2026-01-01', $tz),
+ new DateTime('2029-01-01', $tz),
+ new DateTime('2032-01-01', $tz),
+ new DateTime('2035-01-01', $tz),
+ new DateTime('2038-01-01', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testYearlyLeapYear() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=3';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2012-02-29', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2012-02-29', $tz),
+ new DateTime('2016-02-29', $tz),
+ new DateTime('2020-02-29', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testYearlyByMonth() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-04-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-04-07', $tz),
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2015-04-07', $tz),
+ new DateTime('2015-10-07', $tz),
+ new DateTime('2019-04-07', $tz),
+ new DateTime('2019-10-07', $tz),
+ new DateTime('2023-04-07', $tz),
+ new DateTime('2023-10-07', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testYearlyByMonthByDay() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-04-04', $tz),
+ new DateTime('2011-04-24', $tz),
+ new DateTime('2011-10-03', $tz),
+ new DateTime('2011-10-30', $tz),
+ new DateTime('2016-04-04', $tz),
+ new DateTime('2016-04-24', $tz),
+ new DateTime('2016-10-03', $tz),
+ new DateTime('2016-10-30', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testFastForward() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU';
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ // The idea is that we're fast-forwarding too far in the future, so
+ // there will be no results left.
+ $it->fastForward(new DateTime('2020-05-05', new DateTimeZone('UTC')));
+
+ $max = 20;
+ $result = array();
+ while($item = $it->current()) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+ $it->next();
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+ $this->assertEquals(array(), $result);
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testComplexExclusions() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RRULE = 'FREQ=YEARLY;COUNT=10';
+ $dtStart = $vcal->createProperty('DTSTART');
+
+ $tz = new DateTimeZone('Canada/Eastern');
+ $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz));
+
+ $exDate1 = $vcal->createProperty('EXDATE');
+ $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz)));
+ $exDate2 = $vcal->createProperty('EXDATE');
+ $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz)));
+
+ $ev->add($dtStart);
+ $ev->add($exDate1);
+ $ev->add($exDate2);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,(string)$ev->uid);
+
+ $max = 20;
+ $result = array();
+ foreach($it as $k=>$item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-01-01 13:50:20', $tz),
+ new DateTime('2013-01-01 13:50:20', $tz),
+ new DateTime('2015-01-01 13:50:20', $tz),
+ new DateTime('2017-01-01 13:50:20', $tz),
+ new DateTime('2018-01-01 13:50:20', $tz),
+ new DateTime('2019-01-01 13:50:20', $tz),
+ new DateTime('2020-01-01 13:50:20', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testOverridenEvent() {
+
+ $vcal = new VCalendar();
+
+ $ev1 = $vcal->createComponent('VEVENT');
+ $ev1->UID = 'overridden';
+ $ev1->RRULE = 'FREQ=DAILY;COUNT=10';
+ $ev1->DTSTART = '20120107T120000Z';
+ $ev1->SUMMARY = 'baseEvent';
+
+ $vcal->add($ev1);
+
+ // ev2 overrides an event, and puts it on 2pm instead.
+ $ev2 = $vcal->createComponent('VEVENT');
+ $ev2->UID = 'overridden';
+ $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
+ $ev2->DTSTART = '20120110T140000Z';
+ $ev2->SUMMARY = 'Event 2';
+
+ $vcal->add($ev2);
+
+ // ev3 overrides an event, and puts it 2 days and 2 hours later
+ $ev3 = $vcal->createComponent('VEVENT');
+ $ev3->UID = 'overridden';
+ $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
+ $ev3->DTSTART = '20120115T140000Z';
+ $ev3->SUMMARY = 'Event 3';
+
+ $vcal->add($ev3);
+
+ $it = new EventIterator($vcal,'overridden');
+
+ $dates = array();
+ $summaries = array();
+ while($it->valid()) {
+
+ $dates[] = $it->getDTStart();
+ $summaries[] = (string)$it->getEventObject()->SUMMARY;
+ $it->next();
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+ $this->assertEquals(array(
+ new DateTime('2012-01-07 12:00:00',$tz),
+ new DateTime('2012-01-08 12:00:00',$tz),
+ new DateTime('2012-01-09 12:00:00',$tz),
+ new DateTime('2012-01-10 14:00:00',$tz),
+ new DateTime('2012-01-11 12:00:00',$tz),
+ new DateTime('2012-01-12 12:00:00',$tz),
+ new DateTime('2012-01-14 12:00:00',$tz),
+ new DateTime('2012-01-15 12:00:00',$tz),
+ new DateTime('2012-01-15 14:00:00',$tz),
+ new DateTime('2012-01-16 12:00:00',$tz),
+ ), $dates);
+
+ $this->assertEquals(array(
+ 'baseEvent',
+ 'baseEvent',
+ 'baseEvent',
+ 'Event 2',
+ 'baseEvent',
+ 'baseEvent',
+ 'baseEvent',
+ 'baseEvent',
+ 'Event 3',
+ 'baseEvent',
+ ), $summaries);
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testOverridenEvent2() {
+
+ $vcal = new VCalendar();
+
+ $ev1 = $vcal->createComponent('VEVENT');
+ $ev1->UID = 'overridden';
+ $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
+ $ev1->DTSTART = '20120112T120000Z';
+ $ev1->SUMMARY = 'baseEvent';
+
+ $vcal->add($ev1);
+
+ // ev2 overrides an event, and puts it 6 days earlier instead.
+ $ev2 = $vcal->createComponent('VEVENT');
+ $ev2->UID = 'overridden';
+ $ev2->{'RECURRENCE-ID'} = '20120119T120000Z';
+ $ev2->DTSTART = '20120113T120000Z';
+ $ev2->SUMMARY = 'Override!';
+
+ $vcal->add($ev2);
+
+ $it = new EventIterator($vcal,'overridden');
+
+ $dates = array();
+ $summaries = array();
+ while($it->valid()) {
+
+ $dates[] = $it->getDTStart();
+ $summaries[] = (string)$it->getEventObject()->SUMMARY;
+ $it->next();
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+ $this->assertEquals(array(
+ new DateTime('2012-01-12 12:00:00',$tz),
+ new DateTime('2012-01-13 12:00:00',$tz),
+ new DateTime('2012-01-26 12:00:00',$tz),
+
+ ), $dates);
+
+ $this->assertEquals(array(
+ 'baseEvent',
+ 'Override!',
+ 'baseEvent',
+ ), $summaries);
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testOverridenEventNoValuesExpected() {
+
+ $vcal = new VCalendar();
+ $ev1 = $vcal->createComponent('VEVENT');
+
+ $ev1->UID = 'overridden';
+ $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
+ $ev1->DTSTART = '20120124T120000Z';
+ $ev1->SUMMARY = 'baseEvent';
+
+ $vcal->add($ev1);
+
+ // ev2 overrides an event, and puts it 6 days earlier instead.
+ $ev2 = $vcal->createComponent('VEVENT');
+ $ev2->UID = 'overridden';
+ $ev2->{'RECURRENCE-ID'} = '20120131T120000Z';
+ $ev2->DTSTART = '20120125T120000Z';
+ $ev2->SUMMARY = 'Override!';
+
+ $vcal->add($ev2);
+
+ $it = new EventIterator($vcal,'overridden');
+
+ $dates = array();
+ $summaries = array();
+
+ // The reported problem was specifically related to the VCALENDAR
+ // expansion. In this parcitular case, we had to forward to the 28th of
+ // january.
+ $it->fastForward(new DateTime('2012-01-28 23:00:00'));
+
+ // We stop the loop when it hits the 6th of februari. Normally this
+ // iterator would hit 24, 25 (overriden from 31) and 7 feb but because
+ // we 'filter' from the 28th till the 6th, we should get 0 results.
+ while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) {
+
+ $dates[] = $it->getDTStart();
+ $summaries[] = (string)$it->getEventObject()->SUMMARY;
+ $it->next();
+
+ }
+
+ $this->assertEquals(array(), $dates);
+ $this->assertEquals(array(), $summaries);
+
+ }
+
+ /**
+ * @depends testValues
+ */
+ function testRDATE() {
+
+ $vcal = new VCalendar();
+ $ev = $vcal->createComponent('VEVENT');
+
+ $ev->UID = 'bla';
+ $ev->RDATE = array(
+ new DateTime('2014-08-07', new DateTimeZone('UTC')),
+ new DateTime('2014-08-08', new DateTimeZone('UTC')),
+ );
+ $dtStart = $vcal->createProperty('DTSTART');
+ $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC')));
+
+ $ev->add($dtStart);
+
+ $vcal->add($ev);
+
+ $it = new EventIterator($vcal,$ev->uid);
+
+ // Max is to prevent overflow
+ $max = 12;
+ $result = array();
+ foreach($it as $item) {
+
+ $result[] = $item;
+ $max--;
+
+ if (!$max) break;
+
+ }
+
+ $tz = new DateTimeZone('UTC');
+
+ $this->assertEquals(
+ array(
+ new DateTime('2011-10-07', $tz),
+ new DateTime('2014-08-07', $tz),
+ new DateTime('2014-08-08', $tz),
+ ),
+ $result
+ );
+
+ }
+
+ /**
+ * @depends testValues
+ * @expectedException \InvalidArgumentException
+ */
+ function testNoMasterBadUID() {
+
+ $vcal = new VCalendar();
+ // ev2 overrides an event, and puts it on 2pm instead.
+ $ev2 = $vcal->createComponent('VEVENT');
+ $ev2->UID = 'overridden';
+ $ev2->{'RECURRENCE-ID'} = '20120110T120000Z';
+ $ev2->DTSTART = '20120110T140000Z';
+ $ev2->SUMMARY = 'Event 2';
+
+ $vcal->add($ev2);
+
+ // ev3 overrides an event, and puts it 2 days and 2 hours later
+ $ev3 = $vcal->createComponent('VEVENT');
+ $ev3->UID = 'overridden';
+ $ev3->{'RECURRENCE-ID'} = '20120113T120000Z';
+ $ev3->DTSTART = '20120115T140000Z';
+ $ev3->SUMMARY = 'Event 3';
+
+ $vcal->add($ev3);
+
+ $it = new EventIterator($vcal,'broken');
+
+ }
+}
+
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php
@@ -0,0 +1,65 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01'));
+
+ $result = $vcal->serialize();
+
+ $output = <<assertEquals($output, str_replace("\r","",$result));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php
@@ -0,0 +1,40 @@
+assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal);
+
+ $it = new EventIterator($vcal, 'foo');
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php
@@ -0,0 +1,122 @@
+expand(new DateTime('2014-08-01'), new DateTime('2014-09-01'));
+
+ $expected = <<serialize();
+ $newIcs = str_replace("\r\n","\n", $newIcs);
+ $this->assertEquals(
+ $expected,
+ $newIcs
+ );
+
+
+ }
+
+ function testRemoveFirstEvent() {
+
+ $input = <<expand(new DateTime('2014-08-01'), new DateTime('2014-08-19'));
+
+ $expected = <<serialize();
+ $newIcs = str_replace("\r\n","\n", $newIcs);
+ $this->assertEquals(
+ $expected,
+ $newIcs
+ );
+
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php b/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php
@@ -0,0 +1,56 @@
+assertEquals(
+ $expected,
+ iterator_to_array($it)
+ );
+
+ $this->assertFalse($it->isInfinite());
+
+ }
+
+ function testFastForward() {
+
+ $utc = new DateTimeZone('UTC');
+ $it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc));
+
+ $it->fastForward(new DateTime('2014-08-15 00:00:00'));
+
+ $result = array();
+ while($it->valid()) {
+ $result[] = $it->current();
+ $it->next();
+ }
+
+ $expected = array(
+ new DateTime('2014-09-01 00:00:00', $utc),
+ new DateTime('2014-10-01 00:00:00', $utc),
+ );
+
+ $this->assertEquals(
+ $expected,
+ $result
+ );
+
+ $this->assertFalse($it->isInfinite());
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php b/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php
@@ -0,0 +1,713 @@
+parse(
+ 'FREQ=HOURLY;INTERVAL=3;COUNT=12',
+ '2011-10-07 12:00:00',
+ array(
+ '2011-10-07 12:00:00',
+ '2011-10-07 15:00:00',
+ '2011-10-07 18:00:00',
+ '2011-10-07 21:00:00',
+ '2011-10-08 00:00:00',
+ '2011-10-08 03:00:00',
+ '2011-10-08 06:00:00',
+ '2011-10-08 09:00:00',
+ '2011-10-08 12:00:00',
+ '2011-10-08 15:00:00',
+ '2011-10-08 18:00:00',
+ '2011-10-08 21:00:00',
+ )
+ );
+
+ }
+
+ function testDaily() {
+
+ $this->parse(
+ 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z',
+ '2011-10-07',
+ array(
+ '2011-10-07 00:00:00',
+ '2011-10-10 00:00:00',
+ '2011-10-13 00:00:00',
+ '2011-10-16 00:00:00',
+ '2011-10-19 00:00:00',
+ '2011-10-22 00:00:00',
+ '2011-10-25 00:00:00',
+ )
+ );
+
+ }
+
+ function testDailyByDayByHour() {
+
+ $this->parse(
+ 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7',
+ '2011-10-08 06:00:00',
+ array(
+ '2011-10-08 06:00:00',
+ '2011-10-08 07:00:00',
+ '2011-10-09 06:00:00',
+ '2011-10-09 07:00:00',
+ '2011-10-15 06:00:00',
+ '2011-10-15 07:00:00',
+ '2011-10-16 06:00:00',
+ '2011-10-16 07:00:00',
+ '2011-10-22 06:00:00',
+ '2011-10-22 07:00:00',
+ '2011-10-23 06:00:00',
+ '2011-10-23 07:00:00',
+ )
+ );
+
+ }
+
+ function testDailyByHour() {
+
+ $this->parse(
+ 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15',
+ '2012-10-11 12:00:00',
+ array(
+ '2012-10-11 12:00:00',
+ '2012-10-11 13:00:00',
+ '2012-10-11 14:00:00',
+ '2012-10-11 15:00:00',
+ '2012-10-13 10:00:00',
+ '2012-10-13 11:00:00',
+ '2012-10-13 12:00:00',
+ '2012-10-13 13:00:00',
+ '2012-10-13 14:00:00',
+ '2012-10-13 15:00:00',
+ '2012-10-15 10:00:00',
+ '2012-10-15 11:00:00',
+ )
+ );
+
+ }
+
+ function testDailyByDay() {
+
+ $this->parse(
+ 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR',
+ '2011-10-07 12:00:00',
+ array(
+ '2011-10-07 12:00:00',
+ '2011-10-11 12:00:00',
+ '2011-10-19 12:00:00',
+ '2011-10-21 12:00:00',
+ '2011-10-25 12:00:00',
+ '2011-11-02 12:00:00',
+ '2011-11-04 12:00:00',
+ '2011-11-08 12:00:00',
+ '2011-11-16 12:00:00',
+ '2011-11-18 12:00:00',
+ '2011-11-22 12:00:00',
+ '2011-11-30 12:00:00',
+ )
+ );
+
+ }
+
+ function testDailyCount() {
+
+ $this->parse(
+ 'FREQ=DAILY;COUNT=5',
+ '2014-08-01 18:03:00',
+ array(
+ '2014-08-01 18:03:00',
+ '2014-08-02 18:03:00',
+ '2014-08-03 18:03:00',
+ '2014-08-04 18:03:00',
+ '2014-08-05 18:03:00',
+ )
+ );
+
+ }
+
+ function testDailyByMonth() {
+
+ $this->parse(
+ 'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU',
+ '2007-10-04 16:00:00',
+ array(
+ "2013-09-29 16:00:00",
+ "2013-10-06 16:00:00",
+ "2013-10-13 16:00:00",
+ "2013-10-20 16:00:00",
+ "2013-10-27 16:00:00",
+ "2014-09-07 16:00:00"
+ ),
+ '2013-09-28'
+ );
+
+ }
+
+ function testWeekly() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=2;COUNT=10',
+ '2011-10-07 00:00:00',
+ array(
+ '2011-10-07 00:00:00',
+ '2011-10-21 00:00:00',
+ '2011-11-04 00:00:00',
+ '2011-11-18 00:00:00',
+ '2011-12-02 00:00:00',
+ '2011-12-16 00:00:00',
+ '2011-12-30 00:00:00',
+ '2012-01-13 00:00:00',
+ '2012-01-27 00:00:00',
+ '2012-02-10 00:00:00',
+ )
+ );
+
+ }
+
+ function testWeeklyByDay() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA',
+ '2014-08-01 00:00:00',
+ array(
+ '2014-08-01 00:00:00',
+ '2014-08-04 00:00:00',
+ '2014-08-11 00:00:00',
+ '2014-08-18 00:00:00',
+ )
+ );
+
+ }
+
+ function testWeeklyByDay2() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU',
+ '2011-10-07 00:00:00',
+ array(
+ '2011-10-07 00:00:00',
+ '2011-10-18 00:00:00',
+ '2011-10-19 00:00:00',
+ '2011-10-21 00:00:00',
+ '2011-11-01 00:00:00',
+ '2011-11-02 00:00:00',
+ '2011-11-04 00:00:00',
+ '2011-11-15 00:00:00',
+ '2011-11-16 00:00:00',
+ '2011-11-18 00:00:00',
+ '2011-11-29 00:00:00',
+ '2011-11-30 00:00:00',
+ )
+ );
+
+ }
+
+ function testWeeklyByDayByHour() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10',
+ '2011-10-07 08:00:00',
+ array(
+ '2011-10-07 08:00:00',
+ '2011-10-07 09:00:00',
+ '2011-10-07 10:00:00',
+ '2011-10-18 08:00:00',
+ '2011-10-18 09:00:00',
+ '2011-10-18 10:00:00',
+ '2011-10-19 08:00:00',
+ '2011-10-19 09:00:00',
+ '2011-10-19 10:00:00',
+ '2011-10-21 08:00:00',
+ '2011-10-21 09:00:00',
+ '2011-10-21 10:00:00',
+ '2011-11-01 08:00:00',
+ '2011-11-01 09:00:00',
+ '2011-11-01 10:00:00',
+ )
+ );
+
+ }
+
+ function testWeeklyByDaySpecificHour() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU',
+ '2011-10-07 18:00:00',
+ array(
+ '2011-10-07 18:00:00',
+ '2011-10-18 18:00:00',
+ '2011-10-19 18:00:00',
+ '2011-10-21 18:00:00',
+ '2011-11-01 18:00:00',
+ '2011-11-02 18:00:00',
+ '2011-11-04 18:00:00',
+ '2011-11-15 18:00:00',
+ '2011-11-16 18:00:00',
+ '2011-11-18 18:00:00',
+ '2011-11-29 18:00:00',
+ '2011-11-30 18:00:00',
+ )
+ );
+
+ }
+
+ function testMonthly() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;INTERVAL=3;COUNT=5',
+ '2011-12-05 00:00:00',
+ array(
+ '2011-12-05 00:00:00',
+ '2012-03-05 00:00:00',
+ '2012-06-05 00:00:00',
+ '2012-09-05 00:00:00',
+ '2012-12-05 00:00:00',
+ )
+ );
+
+ }
+
+ function testMonlthyEndOfMonth() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;INTERVAL=2;COUNT=12',
+ '2011-12-31 00:00:00',
+ array(
+ '2011-12-31 00:00:00',
+ '2012-08-31 00:00:00',
+ '2012-10-31 00:00:00',
+ '2012-12-31 00:00:00',
+ '2013-08-31 00:00:00',
+ '2013-10-31 00:00:00',
+ '2013-12-31 00:00:00',
+ '2014-08-31 00:00:00',
+ '2014-10-31 00:00:00',
+ '2014-12-31 00:00:00',
+ '2015-08-31 00:00:00',
+ '2015-10-31 00:00:00',
+ )
+ );
+
+ }
+
+ function testMonthlyByMonthDay() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7',
+ '2011-01-01 00:00:00',
+ array(
+ '2011-01-01 00:00:00',
+ '2011-01-25 00:00:00',
+ '2011-01-31 00:00:00',
+ '2011-06-01 00:00:00',
+ '2011-06-24 00:00:00',
+ '2011-11-01 00:00:00',
+ '2011-11-24 00:00:00',
+ '2012-04-01 00:00:00',
+ '2012-04-24 00:00:00',
+ )
+ );
+
+ }
+
+ function testMonthlyByDay() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH',
+ '2011-01-03 00:00:00',
+ array(
+ '2011-01-03 00:00:00',
+ '2011-01-05 00:00:00',
+ '2011-01-10 00:00:00',
+ '2011-01-17 00:00:00',
+ '2011-01-18 00:00:00',
+ '2011-01-20 00:00:00',
+ '2011-01-24 00:00:00',
+ '2011-01-31 00:00:00',
+ '2011-03-02 00:00:00',
+ '2011-03-07 00:00:00',
+ '2011-03-14 00:00:00',
+ '2011-03-17 00:00:00',
+ '2011-03-21 00:00:00',
+ '2011-03-22 00:00:00',
+ '2011-03-28 00:00:00',
+ '2011-05-02 00:00:00',
+ )
+ );
+
+ }
+
+ function testMonthlyByDayByMonthDay() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1',
+ '2011-08-01 00:00:00',
+ array(
+ '2011-08-01 00:00:00',
+ '2012-10-01 00:00:00',
+ '2013-04-01 00:00:00',
+ '2013-07-01 00:00:00',
+ '2014-09-01 00:00:00',
+ '2014-12-01 00:00:00',
+ '2015-06-01 00:00:00',
+ '2016-02-01 00:00:00',
+ '2016-08-01 00:00:00',
+ '2017-05-01 00:00:00',
+ )
+ );
+
+ }
+
+ function testMonthlyByDayBySetPos() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1',
+ '2011-01-03 00:00:00',
+ array(
+ '2011-01-03 00:00:00',
+ '2011-01-31 00:00:00',
+ '2011-02-01 00:00:00',
+ '2011-02-28 00:00:00',
+ '2011-03-01 00:00:00',
+ '2011-03-31 00:00:00',
+ '2011-04-01 00:00:00',
+ '2011-04-29 00:00:00',
+ '2011-05-02 00:00:00',
+ '2011-05-31 00:00:00',
+ )
+ );
+
+ }
+
+ function testYearly() {
+
+ $this->parse(
+ 'FREQ=YEARLY;COUNT=10;INTERVAL=3',
+ '2011-01-01 00:00:00',
+ array(
+ '2011-01-01 00:00:00',
+ '2014-01-01 00:00:00',
+ '2017-01-01 00:00:00',
+ '2020-01-01 00:00:00',
+ '2023-01-01 00:00:00',
+ '2026-01-01 00:00:00',
+ '2029-01-01 00:00:00',
+ '2032-01-01 00:00:00',
+ '2035-01-01 00:00:00',
+ '2038-01-01 00:00:00',
+ )
+ );
+ }
+
+ function testYearlyLeapYear() {
+
+ $this->parse(
+ 'FREQ=YEARLY;COUNT=3',
+ '2012-02-29 00:00:00',
+ array(
+ '2012-02-29 00:00:00',
+ '2016-02-29 00:00:00',
+ '2020-02-29 00:00:00',
+ )
+ );
+ }
+
+ function testYearlyByMonth() {
+
+ $this->parse(
+ 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10',
+ '2011-04-07 00:00:00',
+ array(
+ '2011-04-07 00:00:00',
+ '2011-10-07 00:00:00',
+ '2015-04-07 00:00:00',
+ '2015-10-07 00:00:00',
+ '2019-04-07 00:00:00',
+ '2019-10-07 00:00:00',
+ '2023-04-07 00:00:00',
+ '2023-10-07 00:00:00',
+ )
+ );
+
+ }
+
+ function testYearlyByMonthByDay() {
+
+ $this->parse(
+ 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU',
+ '2011-04-04 00:00:00',
+ array(
+ '2011-04-04 00:00:00',
+ '2011-04-24 00:00:00',
+ '2011-10-03 00:00:00',
+ '2011-10-30 00:00:00',
+ '2016-04-04 00:00:00',
+ '2016-04-24 00:00:00',
+ '2016-10-03 00:00:00',
+ '2016-10-30 00:00:00',
+ )
+ );
+
+ }
+
+ function testFastForward() {
+
+ // The idea is that we're fast-forwarding too far in the future, so
+ // there will be no results left.
+ $this->parse(
+ 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU',
+ '2011-04-04 00:00:00',
+ array(),
+ '2020-05-05 00:00:00'
+ );
+
+ }
+
+ /**
+ * The bug that was in the
+ * system before would fail on the 5th tuesday of the month, if the 5th
+ * tuesday did not exist.
+ *
+ * A pretty slow test. Had to be marked as 'medium' for phpunit to not die
+ * after 1 second. Would be good to optimize later.
+ *
+ * @medium
+ */
+ function testFifthTuesdayProblem() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU',
+ '2007-10-04 14:46:42',
+ array(
+ "2007-10-04 14:46:42",
+ )
+ );
+
+ }
+
+ /**
+ * This bug came from a Fruux customer. This would result in a never-ending
+ * request.
+ */
+ function testFastFowardTooFar() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1',
+ '2009-04-20 18:00:00',
+ array(
+ '2009-04-20 18:00:00',
+ '2009-04-27 18:00:00',
+ '2009-05-04 18:00:00',
+ '2009-05-11 18:00:00',
+ '2009-05-18 18:00:00',
+ '2009-05-25 18:00:00',
+ '2009-06-01 18:00:00',
+ '2009-06-08 18:00:00',
+ '2009-06-15 18:00:00',
+ '2009-06-22 18:00:00',
+ '2009-06-29 18:00:00',
+ )
+ );
+
+ }
+
+ /**
+ * This also at one point caused an infinite loop. We're keeping the test.
+ */
+ function testYearlyByMonthLoop() {
+
+ $this->parse(
+ 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA',
+ '2012-01-01 15:45:00',
+ array(
+ '2012-02-01 15:45:00',
+ ),
+ '2012-01-29 23:00:00'
+ );
+
+
+ }
+
+ /**
+ * Something, somewhere produced an ics with an interval set to 0. Because
+ * this means we increase the current day (or week, month) by 0, this also
+ * results in an infinite loop.
+ *
+ * @expectedException InvalidArgumentException
+ */
+ function testZeroInterval() {
+
+ $this->parse(
+ 'FREQ=YEARLY;INTERVAL=0',
+ '2012-08-24 14:57:00',
+ array(),
+ '2013-01-01 23:00:00'
+ );
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testInvalidFreq() {
+
+ $this->parse(
+ 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z',
+ '2011-10-07',
+ array()
+ );
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testByDayBadOffset() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA',
+ '2014-08-01 00:00:00',
+ array()
+ );
+
+ }
+
+ function testUntilBeginHAsTimezone() {
+
+ $this->parse(
+ 'FREQ=WEEKLY;UNTIL=20131118T183000',
+ '2013-09-23 18:30:00',
+ array(
+ '2013-09-23 18:30:00',
+ '2013-09-30 18:30:00',
+ '2013-10-07 18:30:00',
+ '2013-10-14 18:30:00',
+ '2013-10-21 18:30:00',
+ '2013-10-28 18:30:00',
+ '2013-11-04 18:30:00',
+ '2013-11-11 18:30:00',
+ '2013-11-18 18:30:00',
+ ),
+ null,
+ 'America/New_York'
+ );
+
+ }
+
+ function testUntilBeforeDtStart() {
+
+ $this->parse(
+ 'FREQ=DAILY;UNTIL=20140101T000000Z',
+ '2014-08-02 00:15:00',
+ array(
+ '2014-08-02 00:15:00',
+ )
+ );
+
+ }
+
+ function testIgnoredStuff() {
+
+ $this->parse(
+ 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2',
+ '2014-08-02 00:15:00',
+ array(
+ '2014-08-02 00:15:00',
+ '2014-08-03 00:15:00',
+ )
+ );
+
+ }
+
+ function testMinusFifthThursday() {
+
+ $this->parse(
+ 'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4',
+ '2015-01-01 00:15:00',
+ array(
+ '2015-01-01 00:15:00',
+ '2015-01-08 00:15:00',
+ '2015-02-05 00:15:00',
+ '2015-03-05 00:15:00'
+ )
+ );
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testUnsupportedPart() {
+
+ $this->parse(
+ 'FREQ=DAILY;BYWODAN=1',
+ '2014-08-02 00:15:00',
+ array()
+ );
+
+ }
+
+ function testIteratorFunctions() {
+
+ $parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13'));
+ $parser->next();
+ $this->assertEquals(
+ new DateTime('2014-08-03 00:00:13'),
+ $parser->current()
+ );
+ $this->assertEquals(
+ 1,
+ $parser->key()
+ );
+
+ $parser->rewind();
+
+ $this->assertEquals(
+ new DateTime('2014-08-02 00:00:13'),
+ $parser->current()
+ );
+ $this->assertEquals(
+ 0,
+ $parser->key()
+ );
+
+ }
+
+ function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') {
+
+ $dt = new DateTime($start, new DateTimeZone($tz));
+ $parser = new RRuleIterator($rule, $dt);
+
+ if ($fastForward) {
+ $parser->fastForward(new DateTime($fastForward));
+ }
+
+ $result = array();
+ while($parser->valid()) {
+
+ $item = $parser->current();
+ $result[] = $item->format('Y-m-d H:i:s');
+
+ if ($parser->isInfinite() && count($result) >= count($expected)) {
+ break;
+ }
+ $parser->next();
+
+ }
+
+ $this->assertEquals(
+ $expected,
+ $result
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics b/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics
@@ -0,0 +1,39 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+X-WR-TIMEZONE:America/New_York
+PRODID:-//www.churchcommunitybuilder.com//Church Community Builder//EN
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+X-WR-CALNAME:Test Event
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+TZNAME:EDT
+DTSTART:19700308T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+TZNAME:EST
+DTSTART:19701101T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:10621-1440@ccbchurch.com
+DTSTART;TZID=America/New_York:20130923T183000
+DTEND;TZID=America/New_York:20130923T203000
+DTSTAMP:20131216T170211
+RRULE:FREQ=WEEKLY;UNTIL=20131118T183000
+CREATED:20130423T161111
+DESCRIPTION:Test Event ending November 11, 2013
+LAST-MODIFIED:20131126T163428
+SEQUENCE:1387231331
+SUMMARY:Test
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
diff --git a/vendor/sabre/vobject/tests/VObject/SlashRTest.php b/vendor/sabre/vobject/tests/VObject/SlashRTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/SlashRTest.php
@@ -0,0 +1,20 @@
+add('test', "abc\r\ndef");
+ $this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize());
+
+ }
+
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php b/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php
@@ -0,0 +1,325 @@
+version = VObject\Version::VERSION;
+ }
+
+ function createStream($data) {
+
+ $stream = fopen('php://memory','r+');
+ fwrite($stream, $data);
+ rewind($stream);
+ return $stream;
+
+ }
+
+ function testICalendarImportValidEvent() {
+
+ $data = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ while($object=$objects->getNext()) {
+ $return .= $object->serialize();
+ }
+ $this->assertEquals(array(), VObject\Reader::read($return)->validate());
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testICalendarImportWrongType() {
+
+ $data = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ }
+
+ function testICalendarImportEndOfData() {
+ $data = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ while($object=$objects->getNext()) {
+ $return .= $object->serialize();
+ }
+ $this->assertNull($object=$objects->getNext());
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testICalendarImportInvalidEvent() {
+ $data = <<createStream($data);
+ $objects = new ICalendar($tempFile);
+
+ }
+
+ function testICalendarImportMultipleValidEvents() {
+
+ $event[] = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ $i = 0;
+ while($object=$objects->getNext()) {
+
+ $expected = <<version//EN
+CALSCALE:GREGORIAN
+$event[$i]
+END:VCALENDAR
+
+EOT;
+
+ $return .= $object->serialize();
+ $expected = str_replace("\n", "\r\n", $expected);
+ $this->assertEquals($expected, $object->serialize());
+ $i++;
+ }
+ $this->assertEquals(array(), VObject\Reader::read($return)->validate());
+ }
+
+ function testICalendarImportEventWithoutUID() {
+
+ $data = <<version//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+DTSTART:20140101T040000Z
+DTSTAMP:20140122T233226Z
+END:VEVENT
+END:VCALENDAR
+
+EOT;
+ $tempFile = $this->createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ while($object=$objects->getNext()) {
+ $return .= $object->serialize();
+ }
+
+ $messages = VObject\Reader::read($return)->validate();
+
+ if ($messages) {
+ $messages = array_map(
+ function($item) { return $item['message']; },
+ $messages
+ );
+ $this->fail('Validation errors: ' . implode("\n", $messages));
+ } else {
+ $this->assertEquals(array(), $messages);
+ }
+ }
+
+ function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents() {
+
+ $timezones = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ $i = 0;
+ while($object=$objects->getNext()) {
+
+ $expected = <<version//EN
+CALSCALE:GREGORIAN
+$timezones
+$event[$i]
+END:VCALENDAR
+
+EOT;
+ $expected = str_replace("\n", "\r\n", $expected);
+
+ $this->assertEquals($expected, $object->serialize());
+ $return .= $object->serialize();
+ $i++;
+
+ }
+
+ $this->assertEquals(array(), VObject\Reader::read($return)->validate());
+ }
+
+ function testICalendarImportWithOutVTIMEZONES() {
+
+ $data = <<createStream($data);
+
+ $objects = new ICalendar($tempFile);
+
+ $return = "";
+ while($object=$objects->getNext()) {
+ $return .= $object->serialize();
+ }
+
+ $messages = VObject\Reader::read($return)->validate();
+ $this->assertEquals(array(), $messages);
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php b/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php
@@ -0,0 +1,195 @@
+createStream($data);
+
+ $objects = new VCard($tempFile);
+
+ $count = 0;
+ while($objects->getNext()) {
+ $count++;
+ }
+ $this->assertEquals(1, $count);
+
+ }
+
+ /**
+ * @expectedException Sabre\VObject\ParseException
+ */
+ function testVCardImportWrongType() {
+ $event[] = <<createStream($data);
+
+ $splitter = new VCard($tempFile);
+
+ while($object=$splitter->getNext()) {
+ }
+
+ }
+
+ function testVCardImportValidVCardsWithCategories() {
+ $data = <<createStream($data);
+
+ $splitter = new VCard($tempFile);
+
+ $count = 0;
+ while($object=$splitter->getNext()) {
+ $count++;
+ }
+ $this->assertEquals(4, $count);
+
+ }
+
+ function testVCardImportEndOfData() {
+ $data = <<createStream($data);
+
+ $objects = new VCard($tempFile);
+ $object=$objects->getNext();
+
+ $this->assertNull($objects->getNext());
+
+
+ }
+
+ /**
+ * @expectedException \Sabre\VObject\ParseException
+ */
+ function testVCardImportCheckInvalidArgumentException() {
+ $data = <<createStream($data);
+
+ $objects = new VCard($tempFile);
+ while($objects->getNext()) { }
+
+ }
+
+ function testVCardImportMultipleValidVCards() {
+ $data = <<createStream($data);
+
+ $objects = new VCard($tempFile);
+
+ $count = 0;
+ while($objects->getNext()) {
+ $count++;
+ }
+ $this->assertEquals(2, $count);
+
+ }
+
+ function testImportMultipleSeparatedWithNewLines() {
+ $data = <<createStream($data);
+ $objects = new VCard($tempFile);
+
+ $count = 0;
+ while ($objects->getNext()) {
+ $count++;
+ }
+ $this->assertEquals(2, $count);
+ }
+
+ function testVCardImportVCardWithoutUID() {
+ $data = <<createStream($data);
+
+ $objects = new VCard($tempFile);
+
+ $count = 0;
+ while($objects->getNext()) {
+ $count++;
+ }
+
+ $this->assertEquals(1, $count);
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/StringUtilTest.php b/vendor/sabre/vobject/tests/VObject/StringUtilTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/StringUtilTest.php
@@ -0,0 +1,55 @@
+assertEquals(false, $string);
+
+ }
+
+ function testIsUTF8() {
+
+ $string = StringUtil::isUTF8('I 💚 SabreDAV');
+
+ $this->assertEquals(true, $string);
+
+ }
+
+ function testUTF8ControlChar() {
+
+ $string = StringUtil::isUTF8(chr(0x00));
+
+ $this->assertEquals(false, $string);
+
+ }
+
+ function testConvertToUTF8nonUTF8() {
+
+ $string = StringUtil::convertToUTF8(chr(0xbf));
+
+ $this->assertEquals(utf8_encode(chr(0xbf)), $string);
+
+ }
+
+ function testConvertToUTF8IsUTF8() {
+
+ $string = StringUtil::convertToUTF8('I 💚 SabreDAV');
+
+ $this->assertEquals('I 💚 SabreDAV', $string);
+
+ }
+
+ function testConvertToUTF8ControlChar() {
+
+ $string = StringUtil::convertToUTF8(chr(0x00));
+
+ $this->assertEquals('', $string);
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/TestCase.php b/vendor/sabre/vobject/tests/VObject/TestCase.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/TestCase.php
@@ -0,0 +1,50 @@
+fail('Input must be a string, stream or VObject component');
+ }
+ unset($input->PRODID);
+ return $input;
+
+ };
+
+ $expected = $getObj($expected);
+ $actual = $getObj($actual);
+
+ $this->assertEquals(
+ $expected->serialize(),
+ $actual->serialize(),
+ $message
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php b/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php
@@ -0,0 +1,369 @@
+assertInstanceOf('DateTimeZone', $tz);
+ } catch (\Exception $e) {
+ if (strpos($e->getMessage(), "Unknown or bad timezone")!==false) {
+ $this->markTestSkipped($timezoneName . ' is not (yet) supported in this PHP version. Update pecl/timezonedb');
+ } else {
+ throw $e;
+ }
+
+ }
+
+ }
+
+ function getMapping() {
+
+ TimeZoneUtil::loadTzMaps();
+
+ // PHPUNit requires an array of arrays
+ return array_map(
+ function($value) {
+ return array($value);
+ },
+ TimeZoneUtil::$map
+ );
+
+ }
+
+ function testExchangeMap() {
+
+ $vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function testWetherMicrosoftIsStillInsane() {
+
+ $vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function testUnknownExchangeId() {
+
+ $vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function testWindowsTimeZone() {
+
+ $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time');
+ $ex = new \DateTimeZone('America/New_York');
+ $this->assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ /**
+ * @dataProvider getPHPTimeZoneIdentifiers
+ */
+ function testTimeZoneIdentifiers($tzid) {
+
+ $tz = TimeZoneUtil::getTimeZone($tzid);
+ $ex = new \DateTimeZone($tzid);
+
+ $this->assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ /**
+ * @dataProvider getPHPTimeZoneBCIdentifiers
+ */
+ function testTimeZoneBCIdentifiers($tzid) {
+
+ $tz = TimeZoneUtil::getTimeZone($tzid);
+ $ex = new \DateTimeZone($tzid);
+
+ $this->assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function getPHPTimeZoneIdentifiers() {
+
+ // PHPUNit requires an array of arrays
+ return array_map(
+ function($value) {
+ return array($value);
+ },
+ \DateTimeZone::listIdentifiers()
+ );
+
+ }
+
+ function getPHPTimeZoneBCIdentifiers() {
+
+ // PHPUNit requires an array of arrays
+ return array_map(
+ function($value) {
+ return array($value);
+ },
+ TimeZoneUtil::getIdentifiersBC()
+ );
+
+ }
+
+ function testTimezoneOffset() {
+
+ $tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true);
+
+ if (version_compare(PHP_VERSION, '5.5.10', '>=') && !defined('HHVM_VERSION')) {
+ $ex = new \DateTimeZone('-04:00');
+ } else {
+ $ex = new \DateTimeZone('Etc/GMT-4');
+ }
+ $this->assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testTimezoneFail() {
+
+ $tz = TimeZoneUtil::getTimeZone('FooBar', null, true);
+
+ }
+
+ function testFallBack() {
+
+ $vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function testLjubljanaBug() {
+
+ $vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+ function testWeirdSystemVLICs() {
+
+$vobj = <<assertEquals($ex->getName(), $tz->getName());
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php b/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php
@@ -0,0 +1,37 @@
+assertTrue(
+ UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555')
+ );
+ $this->assertFalse(
+ UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555')
+ );
+ $this->assertTrue(
+ UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555')
+ );
+ $this->assertFalse(
+ UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555')
+ );
+
+ }
+
+ /**
+ * @depends testValidateUUID
+ */
+ function testGetUUID() {
+
+ $this->assertTrue(
+ UUIDUtil::validateUUID(
+ UUIDUtil::getUUID()
+ )
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/VCard21Test.php b/vendor/sabre/vobject/tests/VObject/VCard21Test.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/VCard21Test.php
@@ -0,0 +1,52 @@
+serialize($input);
+
+ $this->assertEquals($input, $output);
+
+ }
+
+ function testPropertyPadValueCount() {
+
+ $input = <<serialize($input);
+
+ $expected = <<assertEquals($expected, $output);
+
+ }
+}
diff --git a/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php b/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php
@@ -0,0 +1,531 @@
+convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testConvert40to40() {
+
+ $input = <<convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testConvert21to40() {
+
+ $input = <<convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testConvert30to30() {
+
+ $input = <<convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testConvert40to30() {
+
+ $input = <<convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testConvertGroupCard() {
+
+ $input = <<convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ $input = $output;
+ $output = <<convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testBDAYConversion() {
+
+ $input = <<convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ $input = $output;
+ $output = <<convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testUnknownSourceVCardVersion() {
+
+ $input = <<convert(Document::VCARD40);
+
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ function testUnknownTargetVCardVersion() {
+
+ $input = <<convert(Document::VCARD21);
+
+ }
+
+ function testConvertIndividualCard() {
+
+ $input = <<convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ $input = $output;
+ $output = <<convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testAnniversary() {
+
+ $input = <<!$_
+ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
+END:VCARD
+
+OUT;
+
+ $vcard = Reader::read($input);
+ $vcard = $vcard->convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ // Swapping input and output
+ list(
+ $input,
+ $output
+ ) = array(
+ $output,
+ $input
+ );
+
+ $vcard = Reader::read($input);
+ $vcard = $vcard->convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testMultipleAnniversaries() {
+
+ $input = <<!$_
+ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210
+ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210
+ITEM2.X-ABLABEL:_$!!$_
+ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210
+ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210
+ITEM3.X-ABLABEL:_$!!$_
+ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210
+END:VCARD
+
+OUT;
+
+ $vcard = Reader::read($input);
+ $vcard = $vcard->convert(Document::VCARD30);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ // Swapping input and output
+ list(
+ $input,
+ $output
+ ) = array(
+ $output,
+ $input
+ );
+
+ $vcard = Reader::read($input);
+ $vcard = $vcard->convert(Document::VCARD40);
+
+ $this->assertVObjEquals(
+ $output,
+ $vcard
+ );
+
+ }
+
+ function testNoLabel() {
+
+ $input = <<assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard);
+ $vcard = $vcard->convert(Document::VCARD40);
+ $vcard = $vcard->serialize();
+
+ $converted = Reader::read($vcard);
+ $converted->validate();
+
+ $version = Version::VERSION;
+
+ $expected = <<assertEquals($expected, str_replace("\r","", $vcard));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/VersionTest.php b/vendor/sabre/vobject/tests/VObject/VersionTest.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/VersionTest.php
@@ -0,0 +1,14 @@
+assertEquals(-1, version_compare('2.0.0',$v));
+
+ }
+
+}
diff --git a/vendor/sabre/vobject/tests/VObject/issue153.vcf b/vendor/sabre/vobject/tests/VObject/issue153.vcf
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/issue153.vcf
@@ -0,0 +1,352 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Benutzer;Test;;;
+FN:Test Benutzer
+PHOTO;BASE64:
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
+ AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
+ AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
+ Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
+ CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
+ AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
+ kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
+ aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
+ 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
+ YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
+ goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
+ 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
+ F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
+ 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
+ BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
+ 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
+ zbVbk4/OrNpefLsnyyg5UUAf/9k=
+END:VCARD
diff --git a/vendor/sabre/vobject/tests/VObject/issue64.vcf b/vendor/sabre/vobject/tests/VObject/issue64.vcf
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/VObject/issue64.vcf
@@ -0,0 +1,351 @@
+BEGIN:VCARD
+VERSION:2.1
+PHOTO;ENCODING=BASE64;JPEG:
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
+ AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD
+ AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN
+ Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL
+ CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA
+ AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB
+ kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn
+ aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
+ 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
+ YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
+ goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk
+ 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA
+ F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY
+ 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL
+ BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0
+ 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
+ zbVbk4/OrNpefLsnyyg5UUAf/9k=
+
+END:VCARD
diff --git a/vendor/sabre/vobject/tests/bootstrap.php b/vendor/sabre/vobject/tests/bootstrap.php
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/bootstrap.php
@@ -0,0 +1,25 @@
+addPsr4('Sabre\\VObject\\',__DIR__ . '/VObject');
+
+if (!defined('SABRE_TEMPDIR')) {
+ define('SABRE_TEMPDIR', __DIR__ . '/temp/');
+}
+
+if (!file_exists(SABRE_TEMPDIR)) {
+ mkdir(SABRE_TEMPDIR);
+}
diff --git a/vendor/sabre/vobject/tests/phpcs/ruleset.xml b/vendor/sabre/vobject/tests/phpcs/ruleset.xml
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/phpcs/ruleset.xml
@@ -0,0 +1,57 @@
+
+
+ sabre.io codesniffer ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/sabre/vobject/tests/phpunit.xml b/vendor/sabre/vobject/tests/phpunit.xml
new file mode 100644
--- /dev/null
+++ b/vendor/sabre/vobject/tests/phpunit.xml
@@ -0,0 +1,21 @@
+
+
+ VObject/
+
+
+
+
+ ../lib/
+
+ ../lib/Sabre/VObject/includes.php
+
+
+
+